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

#13 Feature: 일정 관련 로직 구현 #14

Merged
merged 46 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
0e6c242
feat: event 엔티티 구현
hyxklee Jul 8, 2024
b3987e9
feat: event 레포지토리 구현, 일정 조회 로직 구현
hyxklee Jul 8, 2024
0221646
feat: event mapper 구현
hyxklee Jul 8, 2024
22d7352
feat: event 응답 dto 구현
hyxklee Jul 8, 2024
86f55b9
feat: event 요청 dto 구현
hyxklee Jul 8, 2024
de5882a
feat: (EventService) 이벤트 저장 로직 구현
hyxklee Jul 8, 2024
13516d7
feat: (EventController) 이벤트 생성 api 구현
hyxklee Jul 8, 2024
7fc76e9
refactor: (EventService) 이벤트 생성 변수명 변경
hyxklee Jul 8, 2024
b42e6bf
refactor: annotation 오타 수정
hyxklee Jul 8, 2024
d6cf99e
refactor: 메소드 명 수정
hyxklee Jul 8, 2024
c88f41b
refactor: location 변수 추가
hyxklee Jul 8, 2024
11bc21e
refactor: (EventService) 일정 상세 조회 로직 구현
hyxklee Jul 8, 2024
780f0e0
refactor: (EventController) 일정 상세 조회 api 구현
hyxklee Jul 8, 2024
beb3e22
refactor: (EventService) 예외처리 로직 수정
hyxklee Jul 8, 2024
88be70c
feat: (EventService) 기간 별 일정 조회 로직 구현
hyxklee Jul 8, 2024
a16a8b9
feat: (EventController) 기간 별 일정 조회 api 구현
hyxklee Jul 8, 2024
701e41c
refactor: 반환 dto에 event id 추가
hyxklee Jul 8, 2024
e1f57ec
feat: (EventService) 일정 수정 로직 구현
hyxklee Jul 8, 2024
d775ff8
feat: (EventController) 일정 수정 api 구현
hyxklee Jul 8, 2024
9605432
refactor: 사용하지 않는 import 문 정리
hyxklee Jul 8, 2024
ed56c78
feat: (EventService) 일정 삭제 로직 구현
hyxklee Jul 8, 2024
bc41e21
feat: (EventController) 일정 삭제 api 구현
hyxklee Jul 8, 2024
83f771d
refactor: events 관련 경로 주석 처리
hyxklee Jul 8, 2024
9e45d04
refactor: 요청 처리 완료 시 메시지 추가
hyxklee Jul 8, 2024
4205924
chore: 디렉토리 구조 변경
hyxklee Jul 8, 2024
d9678bd
docs: PR 템플릿 작성
hyxklee Jul 8, 2024
1128456
refactor: 데이터를 불변적으로 반환하기 위해 toList()로 수정
hyxklee Jul 8, 2024
2a80e14
feat: ErrorMessage를 enum으로 관리
hyxklee Jul 8, 2024
d13e44c
feat: 응답 메시지를 enum으로 관리
hyxklee Jul 8, 2024
3803684
refactor: enum 형태로 응답 메시지를 반환
hyxklee Jul 8, 2024
1f87e02
refactor: enum 형태로 에러 메시지를 생성
hyxklee Jul 8, 2024
1827771
refactor: setter 제거
hyxklee Jul 8, 2024
68b3016
refactor: 빌더 패턴 적용
hyxklee Jul 8, 2024
2890e83
refactor: requestEvent dto를 전달받아 update 하는 방식으로 수정
hyxklee Jul 8, 2024
7913dd3
refactor: requestEvent dto를 전달해 update 하는 방식으로 수정
hyxklee Jul 8, 2024
673e01e
refactor: null 값을 제외한 값만 업데이트 하는 방식으로 변경
hyxklee Jul 9, 2024
d873daf
refactor: null 값을 제외한 값만 업데이트 하는 방식으로 변경
hyxklee Jul 9, 2024
bca5166
feat: swagger 설정 파일 추가
hyxklee Jul 9, 2024
999aafd
feat: PreAuthorize 사용을 위한 설정 추가
hyxklee Jul 9, 2024
3386392
feat: swagger api 명세를 위한 설정 추가, @PreAuthorize 어노테이션 추가(사용자 권한 확인)
hyxklee Jul 9, 2024
f057d03
fix: JWT 토큰으로 인증이 안되는 오류 수정
hyxklee Jul 9, 2024
66c8e2c
refactor: dto를 record로 구현
hyxklee Jul 9, 2024
f852c6a
refactor: dto를 record로 구현함에 따라 값을 가져오는 방식 수정
hyxklee Jul 9, 2024
5d4da54
chore: 주석 삭제
hyxklee Jul 9, 2024
bc9f26a
refactor: 사전 정의된 메서드 제거
hyxklee Jul 9, 2024
a4c5411
refactor: API 엔드포인트 수정
hyxklee Jul 9, 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
10 changes: 10 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
- 제목 : #이슈번호 feat: 기능명
ex) #15 feat: pull request template 작성
(확인 후 지워주세요)

## 1. 개발내용

## 2. 구현 세부사항

## 3. 메모

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package leets.weeth.domain.event.controller;

import leets.weeth.domain.event.dto.RequestEvent;
import leets.weeth.domain.event.dto.ResponseEvent;
import leets.weeth.domain.event.service.EventService;
import leets.weeth.global.common.response.CommonResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/events")
public class EventController {
private final EventService eventService;

// 일정 생성
@PostMapping("/create")
public CommonResponse<String> createEvent(@RequestBody RequestEvent requestEvent) {
eventService.createEvent(requestEvent);
return CommonResponse.createSuccess("일정 생성 성공.");
Copy link
Member

Choose a reason for hiding this comment

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

String 보다는 enum으로 상태관리를 하는건 어떨까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

문자열로 반환하는 부분은 전부 enum으로 변경해서 관리하겠습니다!

}

// 일정 상세 조회
@GetMapping("/{id}")
public CommonResponse<ResponseEvent> getEvent(@PathVariable Long id) {
ResponseEvent responseEvent = eventService.getEventById(id);
return CommonResponse.createSuccess(responseEvent);
}

// 기간 별 일정 조회
@GetMapping("")
public CommonResponse<List<ResponseEvent>> getEvents(
@RequestParam(name = "start") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDate,
@RequestParam(name = "end") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate) {
Comment on lines +48 to +49
Copy link
Member

Choose a reason for hiding this comment

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

ISO.DATA_TIME 으로 맞춘 점이 좋네요! 프론트와 데이터 타입이 맞지 않는 경우가 많은데, 신경쓰신거 같아서 좋은것 같습니다:)


List<ResponseEvent> responseEvents = eventService.getEventsBetweenDate(startDate, endDate);
return CommonResponse.createSuccess(responseEvents);
}

// 일정 수정
Copy link
Member

Choose a reason for hiding this comment

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

궁금한 점이 있는데, 혹시 일정 수정은 아무나 할 수 있는 건가요? 아니면 인증인가 부분을 아직 구현 전이라서 그런걸까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

관리자만 수정할 수 있게 구현할 예정입니다! 아직 인증인가를 구현하지 않았습니다!

@PatchMapping("/update/{id}")
public CommonResponse<String> updateEvent(@PathVariable Long id, @RequestBody RequestEvent requestEvent) {
eventService.updateEvent(id, requestEvent);
return CommonResponse.createSuccess("id: " + id + " 일정 수정 성공");
Copy link
Member

Choose a reason for hiding this comment

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

여기도 마찬가지로 ENUM을 사용하면 더 좋을것 같습니다!

}

// 일정 삭제
@DeleteMapping("/{id}")
Copy link
Member

Choose a reason for hiding this comment

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

create와 update 요청과는 다르게 왜 delete는 주소에 delete가 없는건가요??
POST 요청은 다양한 목적을 가진 요청일 가능성이 크므로 주소에 create가 들어가는 것도 괜찮을 것 같다고 생각해요
하지만 update와 delete는 HTTP 메서드로 충분히 목적을 밝히고 있는 것 같아 굳이 넣을 필요가 없다고 생각했어요
권장하는 RESTful API 설계 중 "행위(method)는 URL에 포함하지 않는다."는 방법을 따르는 건 어떨까요?

참고: https://library.gabia.com/contents/8339/

Copy link
Member Author

Choose a reason for hiding this comment

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

참고해서 수정해보겠습니다

public CommonResponse<String> deleteEvent(@PathVariable Long id) {
eventService.deleteEvent(id);
return CommonResponse.createSuccess("id: " + id + " 일정 삭제 성공");
Copy link
Member

Choose a reason for hiding this comment

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

위와 같아요!

}

}
20 changes: 20 additions & 0 deletions src/main/java/leets/weeth/domain/event/dto/RequestEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package leets.weeth.domain.event.dto;

import lombok.Getter;
import lombok.Setter;

import java.time.LocalDateTime;

@Getter
@Setter
Copy link
Member

Choose a reason for hiding this comment

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

빌더 패턴을 사용하면 추후 dto를 수정하더라도 더 쉽게 코드를 변경할 수 있을것 같아요
한번 생각해보시면 좋을것 같습니다

Copy link
Member Author

Choose a reason for hiding this comment

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

현재 EventMapper 인터페이스를 사용해 dto와 객체 간의 변환을 하고 있어 직접적으로 dto를 생성하여 사용하는 로직이 없는 상황이라서 빌더 패턴을 사용하지 않고 있습니다! dto 수정시 mapper가 자동으로 잡아서 변환할 수 있게 처리 중인데 빌더 패턴을 넣어야하는지 잘 모르겠습니다! 더 고민해보겠습니다! 감사합니다!_

Copy link
Member Author

Choose a reason for hiding this comment

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

빌더 패턴으로 변경 시 mapstruct 라이브러리에서 변환을 해주는 부분이 빌더 패턴에 맞게 수정이 되는 것을 확인했습니다. 기존 setter를 이용해 변환하는 것 보다 이 방식이 나은 것 같아 ResponseEvent 객체에는 빌더 패턴을 적용하는 것이 좋을 거 같습니다! 알려주셔서 감사합니다 하나더 배워갑니다!
image
->
image

Copy link
Member

Choose a reason for hiding this comment

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

record를 통해 DTO 클래스를 구현해준다면 더욱 좋을 것 같습니다!
DTO 클래스가 가져야 하는 모든 책임들을 포함하고 있습니다 :)

Copy link
Member Author

Choose a reason for hiding this comment

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

공부해보고 팀원들과 얘기해서 사용해보겠습니다! 감사합니다!

Copy link
Member

Choose a reason for hiding this comment

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

dto 에 setter는 아래의 이유로 쓰지 않는다고 하네요.
1)Setter 메소드를 사용하면 값을 변경한 의도를 파악하기 힘듭니다.
2)객체의 일관성을 유지하기 어렵습니다.

대신 이렇게 많이 대신 쓰이기도 한다고 합니다.
1)생성자를 오버로딩
2)Builder 패턴 사용
3)정적 팩토리 메소드

꼭 필요한 어노테이션인지 확인하시면 좋을것 같아요:)

Copy link
Member Author

Choose a reason for hiding this comment

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

양 쪽 DTO 모두 사용하지 않아서 setter는 제거하였습니다!

public class RequestEvent {
private String title;

private String content;

private String location;

private LocalDateTime startDateTime;

private LocalDateTime endDateTime;
}
22 changes: 22 additions & 0 deletions src/main/java/leets/weeth/domain/event/dto/ResponseEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package leets.weeth.domain.event.dto;

import lombok.Getter;
import lombok.Setter;

import java.time.LocalDateTime;

@Getter
@Setter
Copy link
Member

Choose a reason for hiding this comment

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

마찬가지로 빌더 패턴 생각해보시면 더 좋을거 같아요

public class ResponseEvent {
private Long id;

private String title;

private String content;

private String location;

private LocalDateTime startDateTime;

private LocalDateTime endDateTime;
}
40 changes: 40 additions & 0 deletions src/main/java/leets/weeth/domain/event/entity/Event.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package leets.weeth.domain.event.entity;

import jakarta.persistence.*;
import leets.weeth.global.common.entity.BaseEntity;
import lombok.*;

import java.time.LocalDateTime;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@ToString
public class Event extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "event_id")
private Long id;

private String title;

private String content;

private String location;

private LocalDateTime startDateTime;

private LocalDateTime endDateTime;

// 일정 수정을 위한 메소드
public void update(String title, String content, String location, LocalDateTime startDateTime, LocalDateTime endDateTime) {
this.title = title;
this.content = content;
this.location = location;
this.startDateTime = startDateTime;
this.endDateTime = endDateTime;
}
Copy link
Member

@rootTiket rootTiket Jul 8, 2024

Choose a reason for hiding this comment

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

수정 메서드라면 인자를 변수 하나하나 받는것 보다 Event 객체를 서로 주고받게 하는건 어떨까요?
일정을 수정한다는 코드라면 더 직관적으로 보일 수 있을것 같아요

Copy link
Member Author

Choose a reason for hiding this comment

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

수정사항을 dto로 받기 때문에 event 객체를 넘기지 않고 dto 객체를 인자로 넘겨 수정하는 방식으로 바꿔봤는데 이 방식은 어떤가요??

Copy link
Member Author

Choose a reason for hiding this comment

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

null 값이 들어오면 수정하지 않게 하기 위해서 다른 방식으로 구현해봤는데 한 번 확인 부탁드립니다!

}
16 changes: 16 additions & 0 deletions src/main/java/leets/weeth/domain/event/mapper/EventMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package leets.weeth.domain.event.mapper;

import leets.weeth.domain.event.dto.RequestEvent;
import leets.weeth.domain.event.dto.ResponseEvent;
import leets.weeth.domain.event.entity.Event;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Mapper(componentModel = "spring")
public interface EventMapper {
EventMapper INSTANCE = Mappers.getMapper(EventMapper.class);

Event fromDto(RequestEvent requestEvent);

ResponseEvent toDto(Event event);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package leets.weeth.domain.event.repository;

import leets.weeth.domain.event.entity.Event;
import org.springframework.data.jpa.repository.JpaRepository;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

public interface EventRepository extends JpaRepository<Event, Long> {

List<Event> findByStartDateTimeBetween(LocalDateTime startDate, LocalDateTime endDate);

Optional<Event> findById(Long id);
Copy link
Member

Choose a reason for hiding this comment

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

SimpleJpaRepository에 이미 구현된 메서드를 다시 구현한 이유가 있나요??

Copy link
Member Author

Choose a reason for hiding this comment

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

몰랐습니다...

}
70 changes: 70 additions & 0 deletions src/main/java/leets/weeth/domain/event/service/EventService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package leets.weeth.domain.event.service;

import jakarta.persistence.EntityNotFoundException;
import leets.weeth.domain.event.dto.RequestEvent;
import leets.weeth.domain.event.dto.ResponseEvent;
import leets.weeth.domain.event.entity.Event;
import leets.weeth.domain.event.mapper.EventMapper;
import leets.weeth.domain.event.repository.EventRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class EventService {
private final EventRepository eventRepository;

private final EventMapper eventMapper;

// 일정 생성
@Transactional
public void createEvent(RequestEvent requestEvent) {
Event event = eventMapper.fromDto(requestEvent);
eventRepository.save(event);
}

// 일정 상세 조회
@Transactional(readOnly = true)
public ResponseEvent getEventById(Long id) {
Event event = eventRepository.findById(id)
Comment on lines +33 to +35
Copy link
Member

Choose a reason for hiding this comment

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

레포지토리에 달았던 코멘트와 같이 SimpleJpaRepository에 구현된 메서드를 가져다 쓰면 이미 @Transactional(readOnly = true)가 적용돼있는데 존재하는 메서드를 다시 구현해서 같은 작업을 다시 처리한 이유가 있나요??

Copy link
Member Author

Choose a reason for hiding this comment

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

이것도 몰랐습니다..

Copy link
Member Author

Choose a reason for hiding this comment

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

@transactional(readOnly = true)는 명시적으로 표시하는 게 좋을 거 같아서 성능상 차이가 없다며 남겨두겠습니다!

.orElseThrow(() -> new EntityNotFoundException("id에 해당하는 일정이 존재하지 않습니다."));
Copy link
Member

Choose a reason for hiding this comment

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

에러 메시지를 enum으로 관리하거나 커스텀 exception 안에 Message를 고정해두는 방식이 관리하기 더 좋을것 같습니다.

Copy link
Member Author

Choose a reason for hiding this comment

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

제가 생각하기엔 커스텀 exception 안에 메시지를 고정하면 다른 도메인에서 사용시 에러 메시지가 명시적으로 전달되기 어려울 거 같아서 도메인 별로 에러 메시지를 enum으로 관리하는 것이 나을 거 같습니다! 해당 방식으로 수정하겠습니다! 다른 방법이 있다면 알려주시면 감사하겠습니다!!

return eventMapper.toDto(event);
}

// 기간 별 일정 조회
@Transactional(readOnly = true)
public List<ResponseEvent> getEventsBetweenDate(LocalDateTime startDate, LocalDateTime endDate) {
List<Event> events = eventRepository.findByStartDateTimeBetween(startDate, endDate);
return events.stream()
.map(eventMapper::toDto)
.collect(Collectors.toList());
Copy link
Member

Choose a reason for hiding this comment

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

데이터의 불변성을 지키기 위해 toList() 를 사용해보시면 좋을 것 같아요 관련 링크 남겨둘게요
https://www.inflearn.com/questions/986386/%EC%8A%A4%ED%8A%B8%EB%A6%BC-tolist%EC%99%80-collect%EC%9D%98-%EC%B0%A8%EC%9D%B4

Copy link
Member Author

Choose a reason for hiding this comment

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

불변성을 위해서 그렇게 하는게 나을 거 같습니다! 수정하겠습니다

}

// 일정 수정
@Transactional
public void updateEvent(Long id, RequestEvent requestEvent) {
Event oldEvent = eventRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("id에 해당하는 일정이 존재하지 않습니다."));
Copy link
Member

Choose a reason for hiding this comment

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

위 코멘트와 마찬가지로 enum으로 관리하면 더 좋을거 같아요


oldEvent.update(
requestEvent.getTitle(),
requestEvent.getContent(),
requestEvent.getLocation(),
requestEvent.getStartDateTime(),
requestEvent.getEndDateTime()
);
}
Copy link
Member

Choose a reason for hiding this comment

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

이부분은 왜 MapStruct을 사용하지 않는건지 알수 있을까요?
update 할 부분이 많아 지는 경우에는 모든부분을 고칠 필요가 없는 것 같아요, 유저에 따라서 일부분만 변경 하고 싶을 수도 있다고 생각합니다. 이부분에 대해서 어떻게 생각하시는지 궁금하구요!

그래서 저같으면, mapstruct을 이미 사용하고 계시기 때문에 request값이 null이 아닌 부분만 update를 할 수 있지 않을까 생각했습니다:)

Copy link
Member Author

Choose a reason for hiding this comment

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

아직 라이브러리에 대한 이해도가 없어서 수정 로직을 어떻게 바꿔야할지 고민 중입니다! 테스트 해보니 현재 방식으로는 null도 같이 업데이트 되고 있어서 더 연구해보고 수정하도록 하겠습니다!

Copy link
Member Author

Choose a reason for hiding this comment

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

찾아보니 MapStruct를 이용해 update 하는 방식은 Event Entity에 Setter를 모두 열어놔야하는 방식이라서 다른 방식으로 구현해봤는데 한 번 확인해주실 수 있으실까요??


// 일정 삭제
public void deleteEvent(Long id) {
if (!eventRepository.existsById(id)) {
throw new EntityNotFoundException("id에 해당하는 일정이 존재하지 않습니다.");
Copy link
Member

Choose a reason for hiding this comment

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

여기도요!

}
eventRepository.deleteById(id);
}
}
2 changes: 2 additions & 0 deletions src/main/java/leets/weeth/global/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
authorize ->
authorize
.requestMatchers("/users/apply").permitAll()
//일정 관련 경로 권한 설정
// .requestMatchers("/events/**").permitAll()
// 스웨거 경로
.requestMatchers("/v3/api-docs", "/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**", "/swagger/**").permitAll()
.anyRequest().authenticated()
Expand Down
Loading