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

[Spring Core] 황승준 미션 제출합니다. #390

Open
wants to merge 106 commits into
base: davidolleh
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
766de30
<FEAT> spring web 의존성 추가
davidolleh Nov 5, 2024
8f583de
<FEAT> index page 응답 구현
davidolleh Nov 5, 2024
ed41d53
<FEAT> index page 응답 테스트 구현
davidolleh Nov 5, 2024
1d148ec
<FEAT> person, reservation entity 구현
davidolleh Nov 5, 2024
58d780c
<FIX> reservation id 자료형 Long으로 변경
davidolleh Nov 5, 2024
4ddedd9
<FIX> person id 자료형 Long으로 변경
davidolleh Nov 5, 2024
a555095
<FEAT> getter 추가 객체 비교 순서 비교 id로 하도록 설정
davidolleh Nov 6, 2024
ceeec41
<FEAT> reservation controller dto 구현
davidolleh Nov 6, 2024
c023e3c
<FEAT> reservation controller 구현
davidolleh Nov 6, 2024
d9ad36b
<FEAT> reservation service 구현
davidolleh Nov 6, 2024
e395263
<FEAT> reservation repository 구현
davidolleh Nov 6, 2024
faa20cf
<FEAT> reservation save 기능 구현
davidolleh Nov 6, 2024
f795d78
<FEAT> ResponseEntity return 하도록 변경
davidolleh Nov 6, 2024
df0f34f
<FEAT> reservation delete 기능 구현
davidolleh Nov 6, 2024
14c2227
<FIX> reservation request, response dto 분리
davidolleh Nov 6, 2024
7dfd9e4
<FIX> return 값 구체적으로 명시
davidolleh Nov 6, 2024
2e56edb
<FIX> file 위치 변경
davidolleh Nov 6, 2024
d7e0290
<FIX> 변수 final 로 변경
davidolleh Nov 6, 2024
8f48143
<FEAT> index 페이지 테스트 구현
davidolleh Nov 6, 2024
c6d091f
<DELETE> controller test 파일들 삭제
davidolleh Nov 6, 2024
75bff4a
<FEAT> exception 메세지 추가
davidolleh Nov 6, 2024
697005d
<FEAT> response header 추가
davidolleh Nov 6, 2024
77a24b6
<FEAT> 예외처리 추가
davidolleh Nov 6, 2024
2aaa48f
<FEAT> 예약 삭제 기능 예외처리
davidolleh Nov 6, 2024
fa1537c
<FEAT> Test code 추가
davidolleh Nov 6, 2024
989cad9
<FIX> 주석 제거
davidolleh Nov 6, 2024
4663949
<FEAT> jdbc, h2 의존성 추가
davidolleh Nov 12, 2024
907e629
<FEAT> h2 database 연결 설정, 테이블 생성
davidolleh Nov 12, 2024
b148d0a
<FEAT> 데이터베이스 연결 테스트
davidolleh Nov 12, 2024
1274a15
<FIX> 파일 이름 수정
davidolleh Nov 12, 2024
4257069
<FIX> 파일 이름 변경
davidolleh Nov 12, 2024
3339d0a
<FIX> 파일 이름 변경
davidolleh Nov 12, 2024
3bab9cb
<FIX> 파일 이름 변경
davidolleh Nov 12, 2024
ed6dc7a
<FEAT> 어노테이션 추가로 의존성 주입
davidolleh Nov 12, 2024
66087ad
<FIX> jdbc template 관련 테스트 분리
davidolleh Nov 12, 2024
005b552
<FEAT> jdbc 조회 테스트 구현
davidolleh Nov 12, 2024
78945f2
<FEAT> H2 database 사용 repository구현
davidolleh Nov 12, 2024
0d68891
<FEAT> jdbc repository insert, delete 기능 구현
davidolleh Nov 12, 2024
fff2d60
<FEAT> jdbc repository insert, delete 테스트 구현
davidolleh Nov 12, 2024
beba048
<FIX> 중복된 익명 함수 필드로 설정
davidolleh Nov 13, 2024
4aae3f0
<FIX> 테스트 통과하도록 수정
davidolleh Nov 13, 2024
33f4f2a
<FIX> 주석 제거
davidolleh Nov 13, 2024
68648b3
<FEAT> 삭제 예외처리 추가
davidolleh Nov 13, 2024
e9a35c2
<FEAT> 예외처리 테스트 추가
davidolleh Nov 13, 2024
8530260
<FIX> 주석 삭제
davidolleh Nov 13, 2024
31ba8b8
<FIX> setter 삭제 후 새로운 객체 생성후 전해주도록 변경
davidolleh Nov 13, 2024
4ef725d
<FEAT> entity id로 값 비교하도록 설정
davidolleh Nov 13, 2024
20480eb
<FEAT> README.md 작성
davidolleh Nov 13, 2024
e6d5f11
<FEAT> README.md 작성
davidolleh Nov 13, 2024
2a3a59f
<REFACTOR> 불필요한 주석 제거
davidolleh Nov 20, 2024
b69af71
<REFACTOR> 정적 페이지 api, json api 분리
davidolleh Nov 20, 2024
8b5e45b
<REFACTOR> return 형태 일관성 있게 수정
davidolleh Nov 20, 2024
6260905
<REFACTOR> 코드 한줄로 처리하기보다 문맥에 따라 구분
davidolleh Nov 20, 2024
3b685e9
<REFACTOR> 데이터 의미에 맞게 함수 인자 수정
davidolleh Nov 20, 2024
cb72257
<REFACTOR> 불필요한 코드 제거
davidolleh Nov 20, 2024
dc90448
<REFACTOR> Repository 추상화
davidolleh Nov 20, 2024
8991c9d
<REFACTOR> Repository 추상화
davidolleh Nov 20, 2024
753473e
<REFACTOR> 특정하지 않은 예외 처리 추가
davidolleh Nov 20, 2024
98dfab5
<REFACTOR> 알아볼거 주석처리
davidolleh Nov 20, 2024
1c77107
<REFACTOR> 사용안하는 변수 제거
davidolleh Nov 20, 2024
c00780f
<REFACTOR> 사용안하는 변수 제거
davidolleh Nov 20, 2024
f430a17
<REFACTOR> 정적페이지 리턴, json 리턴 분리
davidolleh Nov 22, 2024
f3a8644
<REFACTOR> ok로 수정
davidolleh Nov 22, 2024
36be467
<REFACTOR> validation 패키치 의존성 추가
davidolleh Nov 22, 2024
313bf0d
<REFACTOR> spring validation으로 dto 필드 예외처리하도록 수정
davidolleh Nov 22, 2024
305af35
<REFACTOR> DB에 맞춰 테스트 코드 수정
davidolleh Nov 22, 2024
351d38a
<REFACTOR> 사용안하는 주석 삭제
davidolleh Nov 22, 2024
0147fd8
<REFACTOR> Comparator삭제
davidolleh Nov 22, 2024
d79f47b
<REFACTOR> 예상하지 못한 예외 500에러 던지도록 수정
davidolleh Nov 22, 2024
05284cc
<REFACTOR> 이름 변경
davidolleh Nov 25, 2024
5080574
<REFACTOR> 예상치 못한 예외처리 추가
davidolleh Nov 25, 2024
a3fcedb
<REFACTOR> log 의존성 추가
davidolleh Nov 25, 2024
d02b685
<REFACTOR> log 설정 구현
davidolleh Nov 25, 2024
22bd9ca
<REFACTOR> findById 예외 처리 null로 처리
davidolleh Nov 26, 2024
4767ed0
<FEAT> 8단계 테스트 추가
davidolleh Nov 26, 2024
8b26c63
<FEAT> time 페이지 api 구현
davidolleh Nov 26, 2024
8ff0ae2
<FEAT> ReservationTime entity, dao 구현
davidolleh Nov 26, 2024
46bb758
<FEAT> 시간 예약 기능 구현
davidolleh Nov 27, 2024
836b55d
<FEAT> 시작 테이블 생성 sql 수정
davidolleh Nov 27, 2024
b580189
<FEAT> 시작 데이터베이스 테이블 초기화 구현
davidolleh Nov 27, 2024
5f7eb86
<FEAT> Core 9단계 구현
davidolleh Nov 27, 2024
77ad816
<FEAT> validation annotation 관련 예외처리 추가
davidolleh Nov 27, 2024
a622f68
<FIX> 페이지 변경
davidolleh Nov 27, 2024
65442d4
<FIX> 변수명 변경
davidolleh Nov 27, 2024
d52825c
<FIX> Reservation entity field time entity 추가
davidolleh Nov 27, 2024
f9f7fe5
<FIX> 쿼리 변경
davidolleh Nov 27, 2024
85d38c0
<DELETE> 불필요한 코드 삭제
davidolleh Nov 27, 2024
5ef7aba
<DELETE> 불필요한 코드 삭제
davidolleh Nov 27, 2024
83aa536
<DELETE> 불필요한 코드 삭제
davidolleh Nov 27, 2024
6b5bddc
<FEAT> 10단계 테스트 추가
davidolleh Nov 27, 2024
3682464
<FIX> Time Reservation 분리
davidolleh Nov 27, 2024
ad5cd1c
<FEAT> 데이터 초기화 설정
davidolleh Nov 27, 2024
cefa3e4
<FIX> rowMapper 에러 해결
davidolleh Nov 27, 2024
9d7da74
<FEAT> 예약 저장 기능 구현
davidolleh Nov 27, 2024
355adbd
<FEAT> 시간 삭제 예외처리 구현
davidolleh Nov 27, 2024
1e6a583
<FIX> 비즈니스 로직에 맞춰 테스트 코드 수정
davidolleh Nov 27, 2024
5bfdb4f
<FIX> null return 대신 예외처리로 변경
davidolleh Nov 27, 2024
841d5a9
<FEAT> README 작성
davidolleh Nov 28, 2024
a07d6ca
<FEAT> README 작성
davidolleh Nov 28, 2024
a1c0e36
병합 해결
davidolleh Nov 28, 2024
a7c6304
conflict 해결
davidolleh Nov 28, 2024
941bd0a
<REFACTOR> Optional 처리
davidolleh Nov 30, 2024
00e7cc5
<REFACTOR> 예약 관련 비즈니스 로직 추가 구현
davidolleh Jan 15, 2025
f3250e9
<REFACTOR> 예약 관련 비즈니스 로직에 맞게 테스트 코드 수정
davidolleh Jan 15, 2025
b2e5533
Merge remote-tracking branch 'origin/davidolleh-core' into davidolleh…
davidolleh Jan 15, 2025
08c66f3
<REFACTOR> 불필요한 파일 삭제
davidolleh Jan 15, 2025
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
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# 간단 스프링 어플리케이션

## Core
### 8단계
- [x] 시간 테이블 설정
- [x] 시간 조회, 추가, 삭제 기능 추가
- [x] DB 초기 값 설정
### 9단계
- [x] 해당 시간이 존재하는 예약이 남아있을 때, 시간 삭제 못하도록 예외처리
- [x] 중복된 시간(시간의 time값이 같은) DB에 저장하지 못하도록 예외처리
### 10단계

### 고민사항
- 프로젝트 구조:<br/>
[도메인 우선 vs 레이어 우선](https://codewithandrea.com/articles/flutter-project-structure/)
<br/>
우선 프로젝트 구조를 잘 가져가면 가져올 수 있는 효과가 무엇이 있는지 궁금합니다
미션처럼 크기가 작은 프로젝트에서는 레이어 구조 또한 괜찮은 방향 같지만 서비스 크기가
큰 앱을 개발하는데 있어서는 도메인 단위로 나누는 것이 프로젝트으 복잡성을 줄일 수 있게 되는거 같습니다.
그러나 도메인 단위로 나뉘게 된다면 중복된 Entity(테이블이 서로 연관되어 있을 가능성이 크기 )에 대한 관리를 어떻게 하는지 궁금합니다!
Comment on lines +14 to +20

Choose a reason for hiding this comment

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

저번에 이야기 나눴을 때, 아키텍쳐를 많이 고민했다고 애기하셨었는데 이 질문에서 딥하게 고민하고 계신 것이 느껴지네요. 👍 👍 👍

Choose a reason for hiding this comment

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

1️⃣ 질문

우선 프로젝트 구조를 잘 가져가면 가져올 수 있는 효과가 무엇이 있는지 궁금합니다

프로젝트 구조를 도메인 중심으로 가져갈지 레이어 중심으로 가져갈지 보다, 이 질문을 우선적으로 해주셔서 정말 감사합니다. 정말 훌륭합니다. 🎉
그쵸. 효과에 공감하고 느껴야 동기가 생기겠죠. 구조, 아키텍쳐 같은 최소 약속을 지키면서 코딩을 하는 것은 매우 번거로운 일이니까요.

그런 말이 있습니다 (진짜 있는지 모름) 개발자는 움직이는 기차의 바퀴를 바꾸는 것이다
모든 것을 허물고 다시 구조를 내 맘대로 올리는 것은 그리 어려운 일은 아니겠어요.
하지만, 우리가 개발자로서 하는 일은 대부분 다른 사람(혹은 과거의 나)이 만든 코드를 이해하고 기존의 비즈니스가 잘 유지된 상태로 개선을 해나가야합니다.
작은 토이 프로젝트도 몇주 몇달만 되어도 엄청난 복잡도가 생길거에요.

저는 구조를 잘 가져간다 는 것은 의존성을 잘 설정한다 인 것 같은데요.
의존성영향(변경)이 전파된다는 의미와 또 같은 의미입니다.

예를 들어 이런 의존 관계가 있겠죠.

  • Service가 Repository를 의존
  • Reservation이 Time을 의존

이는,
Repository의 문제가 생기면 Service에도 문제가 생긴다는 의미입니다.
Time에게 변경이 생기면 Reservation에도 자연스레 변경이 생기겠죠.
(예를들면 Time의 클래스명을 ReservationTime으로 바꾼다고 생각해보면 되겠어요.)

구조를 잘 가져가게 되면 유지보수 시, 변경사항의 전파를 예측하기 좋겠죠.


(영상자료)toss SLASH 22 - 김재민님 / 지속 성장 가능한 코드를 만들어가는 방법

Copy link

@dooboocookie dooboocookie Nov 28, 2024

Choose a reason for hiding this comment

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

2️⃣ 질문

미션처럼 크기가 작은 프로젝트에서는 레이어 구조 또한 괜찮은 방향 같지만 서비스 크기가
큰 앱을 개발하는데 있어서는 도메인 단위로 나누는 것이 프로젝트으 복잡성을 줄일 수 있게 되는거 같습니다.

(구조를 패키지로 많이 나타내므로 이하 패키지라는 표현을 많이 사용 예정)

오! 레이어 중심으로 구조를 가져가는 것도 정말 직관적이고 좋죠!
꼭 규모가 큰 도메인이라 해도 도메인 중심으로 패키지를 나누지 않을 수 있습니다.
레이어 별 나누고 그 안에서 도메인을 따로 나눌 수 있어요. (정답은 없는 것이니까요)

승준님이 그렇게 느끼신 것은 합리적이라 생각합니다.
아마도 규모가 클 수록 팀별로 도메인을 따로 관리할 가능성이 있겠죠.
그렇다 보면,
A팀은 예약 시스템,
B팀은 채팅 시스템,
C팀은 결제 시스템,
...
이런식으로 팀별로 도메인이 나눠질 수 있습니다
서비스 전체로 보면 예약을 하면서 채팅을하고 결제를 하겠지만, 그를 관리하는 사람(팀)이 달라지면 경계가 필요합니다.

이때부턴 도메인 중심적인 사고를 하면서 구조를 짜볼 필요가 있겠죠.

Choose a reason for hiding this comment

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

3️⃣ 질문

그러나 도메인 단위로 나뉘게 된다면 중복된 Entity(테이블이 서로 연관되어 있을 가능성이 크기 )에 대한 관리를 어떻게 하는지 궁금합니다!

답변에 앞서 중복된 Entity(테이블이 서로 연관되어 있을 가능성이 크기)라는 표현이 어떤 의미인지 정확하게 이해하지 못했습니다.
제가 이해한 질문은 도메인 중심 설게에서 다른 패키지로 나뉘어버린 Entity간 연관관게는 어떻게 핸들링하냐입니다.
이 질문이라고 생각하고 답변하겠습니다.

일단, 대부분의 조직에서는
예약 팀결제 팀 엔티티 자체직접 참조하지 않을 것입니다.
결제팀 코드 수정에 예약팀 코드가 영향을 받는 등의 의존성 문제로 참조를 끊는 것이죠.

다만, 예약에서 어떤 결제건이 연관되었는지 알아야한다면, 참조를 하긴 해야겠죠.
직접 참조하는 것보단 약한 참조인 id만을 갖고 있을 수 있겠네요. 이를 간접 참조라고도 합니다.

프로젝트 구조를 도메인 중심적으로 가져가기로 했고,
그 도메인 모델들이 경계가 구분되어서,
다른 패키지에 속했다면,
그 둘은 크게 서로 신경 쓸 필요가 없다는 의미입니다.
이 정도의 질의(메시지)는 던져볼 수 있겠네요. 👍
나 아이디가 1인 예약인데, 내 결제는 아이디가 a래. a결제 결제 상태좀 알려줄래?

이렇게 고민을 깊게 해보는 자세 정말 좋습니다.

질문하신 내용이 이 미션에서 고민할 수 있는 영역 중 꽤 고수준의 영역이라고 생각합니다.
특히 3번 질문답에는 너무 깊게 매몰되기 보다는 그렇구나 정도로 넘어가도 좋겠어요.

Choose a reason for hiding this comment

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

모두가 봤으면 하는 훌륭한 리뷰에요.
좋은 질문과 좋은 답변입니다..👍 감사합니다

Copy link
Author

Choose a reason for hiding this comment

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

저의 가려웠던 부분을 시원하게 긁어 주는 리뷰였습니다 :) 감사드립니다...
계속해서 보고 또 보는 것 같네요 ㅎㅎ


[Repository 계층, 도메인과 영속성 엔티티 사이의 간극](https://kokodakadokok.tistory.com/entry/Repository-%EA%B3%84%EC%B8%B5-%EB%8F%84%EB%A9%94%EC%9D%B8%EA%B3%BC-%EC%97%94%ED%8B%B0%ED%8B%B0-%EC%82%AC%EC%9D%B4%EC%9D%98-%EA%B0%84%EA%B7%B9-%EB%A7%A4%EA%BE%B8%EA%B8%B0)
<br/>
스프링은 기술적으로 편의를 위해서?
데이터베이스 테이블과 Java의 class를 매핑해준 Jpa 기술을 사용하는 것으로 알고 있습니다.
Jpa의 @Entity라는 annotation을 사용하여 정의하는 Class는 Domain과의 간극이 존재한다고
생각합니다. 아직 이 간극에 대해 완벽히 알지 못하여서 Entity와 Domain을 혼돈해서 사용
하게 되는거 같습니다. 최소한 Service를 나눌때 Domain기준으로 나누고 싶은데
Entity(Table)단위로 나뉘게 되는 경향이 강한거 같습니다!
지금은 TimeService와 ReservationService가 나뉘어져 있지만
TimeService와 ReservationService가 하고 있는 역할은 결국 예약이라는 하나의 도메인에 속한다는 생각이 들어
이 둘을 하나의 ReservationService로 합할까 고민하게 되었습니다.

- Entity 패키지를 따로 둔 이유 <br/>
제가 따로 Entity 패키지를 분리한 이유는 Service, Repository, Contoller(controller에서 dto를 entity로 변환하기에) 모두다
Entity를 바라보기 때문에 분리하게 되었습니다.
Comment on lines +22 to +36

Choose a reason for hiding this comment

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

1️⃣ 질문

Repository 계층, 도메인과 영속성 엔티티 사이의 간극
스프링은 기술적으로 편의를 위해서?
데이터베이스 테이블과 Java의 class를 매핑해준 Jpa 기술을 사용하는 것으로 알고 있습니다.
Jpa의 @entity라는 annotation을 사용하여 정의하는 Class는 Domain과의 간극이 존재한다고
생각합니다. 아직 이 간극에 대해 완벽히 알지 못하여서 Entity와 Domain을 혼돈해서 사용
하게 되는거 같습니다.

뭐죠? 도대체 저 글은 어떤 애송이가 쓴 글이죠?

저희가 Reservation 같은 아이들을 부를 때 아래와 같이 정말 여러 이름으로 부를 수 있겠네요.

  • Domain
  • Entity
  • Table
  • Resource
  • ...

저 용어들이 사용되는 때에 따라서 의미가 약간씩은 다르게 사용되는데요.
저 나름대로 해당 단어들을 정의해보겠습니다. (틀릴 수 있음)

  • Domain
    • 해당 비즈니스(프로덕트)가 해결하고자 하는 영역을 의미합니다. 현재는 예약이라고 할 수 있겠네요. 좀 큰 범위네요.
  • Entity
    • Domain에서 의미있는(구분할 필요가 있는) 대상(실체, 객체, ...) 라고 할 수 있겠어요. Domain 보다는 조금 더 code에 가까운 개념이네요.
  • Table
    • 보통 RDBMS에서 엔티티를 다룰 때 사용하는 것이네요. 조금 DB쪽에 가까운 개념이네요.
  • Resource
    • CRUD API 에서 조회, 생성, 수정하려는 대상을 보통 resource라고 지칭합니다.
    • GET /api/v1/reservations/1 는 reservations라는 예약 리스트 리소스에서 1번 리소스를 조회한 것이겠네요

이런 관점에서 Time과 Reservation이 같은 도메인 문제를 해결하기 위한 엔티티들이라면 한 팀(패키지)에서 관리할 수 있겠네요.
근데 그렇다고 서비스 자체가 합쳐지는 것은 또 다른 문제입니다.
그럼 예약 도메인이 복잡해질 수록 ReservationService는 한없이 비대해질테니까요.

지금 제가 생각했을 때는, Service들의 문제라기 보단,
Reservation과 Time을 너무 테이블과 일치시켜 생각하시는 것 같습니다.

RDBMS가 아니라 인메모리에 저장한다고 생각하고 table 개념을 제거하고 설계해보는 것도 방법이겠어요.
그 후에 Repository만 RDB로 갈아끼면되니까요! (오... 갓프링)

Choose a reason for hiding this comment

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

2️⃣ 질문

Entity 패키지를 따로 둔 이유
제가 따로 Entity 패키지를 분리한 이유는 Service, Repository, Contoller(controller에서 dto를 entity로 변환하기에) 모두다
Entity를 바라보기 때문에 분리하게 되었습니다.

그렇군요. 이견 없습니다.

image

위에서 레이어드 아키텍처의 가치에도 크게 벗어나지 않습니다.
Entity는 Domain Model로
영속-어플리케이션-표현 레이어에서 약간은 떨어져 온전히 비즈니스 규칙을 담고있는 아이니까요.
제일 안쪽에 있다고 할 수 있습니다. 모든 레이어들이 entity를 의존하고있죠.

조금 더 자세한 리뷰는 상희님 리뷰 참고하면 좋겠어요.

Copy link
Author

@davidolleh davidolleh Jan 15, 2025

Choose a reason for hiding this comment

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

이런 관점에서 Time과 Reservation이 같은 도메인 문제를 해결하기 위한 엔티티들이라면 한 팀(패키지)에서 관리할 수 있겠네요.
근데 그렇다고 서비스 자체가 합쳐지는 것은 또 다른 문제입니다.
그럼 예약 도메인이 복잡해질 수록 ReservationService는 한없이 비대해질테니까요.

하나의 도메인으로 정의했다고 하나의 서비스만 사용할 것인가? X
이유: 도메인에 대한 복잡성을 떨어뜨리기 위함



## JDBC
### 5단계
- [x] 데이터베이스 설정
- [x] 데이터베이스 연결

### 6단계
- [x] 데이터 조회하기

### 7단계
- [x] 데이터 추가하기
- [x] 데이터 삭제하기
- [x] 데이터 삭제 잘못된 요청시 예외처리

### 7 단계 고민
- Entity id에 setter 함수를 두면 위험성이 크다는 생각이 들어 새로운 객체를 생성해서 return 해야 되겠다 라는 생각을 가지게 되었습니다
과연 spring은 새로 db에 생성된 entity에 관해 어떻게 id를 주입하나 궁금함군요 🤔

### 질문사항
- 어플리케이션 실행중 어디서든 exception이 발생하면 ControllerAdvice를 exception과 관련된 응답을 전달해줍니다.
대부분의 exception을 최종적으로 ControllerAdvice에서 처리를 하다 보니 Service, Repository, Entity 등등 아무데서나 exception을
던질 수 있겠다라는 착각을 하게 되는것 같습니다. 혹시 exception은 보통 어느 layer에서 처리를 하는지 궁금하며 예외처리는 어떻게 관리
되는지 궁금합니다!

11 changes: 11 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,22 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-log4j2'

runtimeOnly 'com.h2database:h2'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:5.3.1'
}

configurations {
configureEach {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
}
}

test {
useJUnitPlatform()
}
41 changes: 24 additions & 17 deletions src/main/java/roomescape/api/ReservationController.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package roomescape.api;

import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import roomescape.api.dto.ReservationRequestDto;
import roomescape.api.dto.ReservationResponseDto;
import roomescape.entity.Reservation;
import roomescape.repository.ReservationRepositoryImpl;
import roomescape.service.ReservationService;

import java.util.List;
Expand All @@ -16,30 +17,38 @@ public class ReservationController {

private final ReservationService reservationService;

public ReservationController() {
this.reservationService = new ReservationService(new ReservationRepositoryImpl());
public ReservationController(
@Autowired ReservationService reservationService
) {
this.reservationService = reservationService;
}

@GetMapping("/reservations")
public ResponseEntity<List<ReservationResponseDto>> readReservations() {
List<Reservation> reservations = reservationService.readReservations();
return ResponseEntity
.ok()
.body(
reservationService.readReservations().stream().
map(ReservationResponseDto::fromEntity)
.toList()
);
}

List<ReservationResponseDto> response
= reservations.stream()
.map(ReservationResponseDto::fromEntity)
.toList();
@GetMapping("/reservations/{id}")
public ResponseEntity<ReservationResponseDto> readReservations(@PathVariable Long id) {
Reservation reservation = reservationService.readReservation(id);

return ResponseEntity
.status(HttpStatus.OK)
.body(response);
.ok()
.body(ReservationResponseDto.fromEntity(reservation));
}

@PostMapping("/reservations")
public ResponseEntity<ReservationResponseDto> createReservation(@RequestBody ReservationRequestDto reservationDto) {
Reservation reservation = reservationService.createReservation(reservationDto.toEntity());

public ResponseEntity<ReservationResponseDto> createReservation(
@RequestBody @Valid ReservationRequestDto reservationDto
) {
ReservationResponseDto response =
ReservationResponseDto.fromEntity(reservation);
ReservationResponseDto.fromEntity(reservationService.createReservation(reservationDto.toEntity()));

return ResponseEntity
.status(HttpStatus.CREATED)
Expand All @@ -50,8 +59,6 @@ public ResponseEntity<ReservationResponseDto> createReservation(@RequestBody Res
@DeleteMapping("/reservations/{id}")
public ResponseEntity<Void> deleteReservation(@PathVariable Long id) {
reservationService.deleteReservation(id);
return ResponseEntity
.status(HttpStatus.NO_CONTENT)
.build();
return ResponseEntity.noContent().build();
}
}
8 changes: 7 additions & 1 deletion src/main/java/roomescape/api/StaticPageController.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@ public String mainPage() {
return "home";
}


@GetMapping("/reservation")
public String reservationPage() {
return "reservation";
return "new-reservation";
}

@GetMapping("/time")
public String timesPage() {
return "time";
}
}
57 changes: 57 additions & 0 deletions src/main/java/roomescape/api/TimeController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package roomescape.api;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import roomescape.api.dto.TimeRequestDto;
import roomescape.api.dto.TimeResponseDto;
import roomescape.entity.Time;
import roomescape.service.TimeService;

import java.util.List;

@RestController
@RequestMapping("/times")
public class TimeController {

private final TimeService timeService;

public TimeController(
@Autowired TimeService timeService
) {
this.timeService = timeService;
}
@GetMapping
public ResponseEntity<List<TimeResponseDto>> readTimes() {
List<Time> times = timeService.readReservationTimes();
List<TimeResponseDto> timeResponseDtos = times.stream()
.map(TimeResponseDto::fromEntity)
.toList();

return ResponseEntity
.ok()
.body(timeResponseDtos);
}

@PostMapping
public ResponseEntity<TimeResponseDto> createTime(@RequestBody TimeRequestDto timeRequestDto) {
Time time = timeService.createReservationTime(TimeRequestDto.toEntity(timeRequestDto));
TimeResponseDto timeResponseDto = TimeResponseDto.fromEntity(time);

String headerName = "Location";
String headerValue = "/times/" + timeResponseDto.id();

return ResponseEntity
.status(HttpStatus.CREATED)
.header(headerName, headerValue)
.body(timeResponseDto);
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteTime(@PathVariable Long id) {
timeService.deleteReservationTime(id);

return ResponseEntity.noContent().build();
}
}
38 changes: 12 additions & 26 deletions src/main/java/roomescape/api/dto/ReservationRequestDto.java
Original file line number Diff line number Diff line change
@@ -1,43 +1,29 @@
package roomescape.api.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import roomescape.entity.Person;
import roomescape.entity.Reservation;
import roomescape.util.DateTimeFormat;
import roomescape.entity.Time;
import roomescape.util.CustomDateTimeFormat;

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;

public record ReservationRequestDto(
@NotBlank
@NotNull
String name,
@NotBlank
@NotNull
String date,
String time
@NotNull
Long time
) {

public ReservationRequestDto {
checkValidation(name, date, time);
}

private void checkValidation(String name, String date, String time) {
blanksValidation(name, date, time);
}

private void blanksValidation(String... fields) {
boolean isBlankExists = Arrays.stream(fields)
.anyMatch(String::isBlank);

if (isBlankExists) {
throw new IllegalArgumentException("요청 인자의 값을 빈값일 수 없습니다");
}
}


public Reservation toEntity() {
return new Reservation(
new Person(this.name),
LocalDate.parse(date, DateTimeFormatter.ofPattern(DateTimeFormat.dateFormat)),
LocalTime.parse(time, DateTimeFormatter.ofPattern(DateTimeFormat.timeFormat))
LocalDate.parse(date, CustomDateTimeFormat.dateFormatter),
new Time(time)
);
}
}
10 changes: 4 additions & 6 deletions src/main/java/roomescape/api/dto/ReservationResponseDto.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
package roomescape.api.dto;
import roomescape.entity.Reservation;
import roomescape.util.DateTimeFormat;

import java.time.format.DateTimeFormatter;
import roomescape.util.CustomDateTimeFormat;

public record ReservationResponseDto(
Long id,
String name,
String date,
String time
TimeResponseDto time
) {
public static ReservationResponseDto fromEntity(Reservation reservation) {
return new ReservationResponseDto(
reservation.getId(),
reservation.getPerson().getName(),
reservation.getDate().format(DateTimeFormatter.ofPattern(DateTimeFormat.dateFormat)),
reservation.getTime().format(DateTimeFormatter.ofPattern(DateTimeFormat.timeFormat))
reservation.getDate().format(CustomDateTimeFormat.dateFormatter),
TimeResponseDto.fromEntity(reservation.getTime())
);
}
}
15 changes: 15 additions & 0 deletions src/main/java/roomescape/api/dto/TimeRequestDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package roomescape.api.dto;

import roomescape.entity.Time;

import java.time.LocalTime;

public record TimeRequestDto(
String time
) {
public static Time toEntity(TimeRequestDto timeRequestDto) {
return new Time(
LocalTime.parse(timeRequestDto.time)
);
}
}
17 changes: 17 additions & 0 deletions src/main/java/roomescape/api/dto/TimeResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package roomescape.api.dto;

import roomescape.entity.Time;
import roomescape.util.CustomDateTimeFormat;

public record TimeResponseDto(
Long id,
String time
) {

public static TimeResponseDto fromEntity(Time reservationTime) {
return new TimeResponseDto(
reservationTime.getId(),
reservationTime.getTime().format(CustomDateTimeFormat.timeFormatter)
);
}
}
Loading