티스토리 뷰
개발 들어가기 앞서 요구사항을 완벽히 분석하고 설계한 후 개발에 들어가진 못했다. 변명을 하자면... 그런 습관이 잘 들어있지 않았고 시간이 부족했다. 그래서 개발을 하다가 더 좋은 방법이 생각나 중간 중간 변경하기도 했다. 채점자 리뷰에서도 이 부분과 관련해 지적을 받은게 있어서 앞으로 설계에 대해 신경을 좀 더 써야겠다고 생각했다.
DB 설계
테이블
테이블 이름 | 역할 |
---|---|
song | 노래 정보 저장 |
album | 앨범정보 저장 |
playlist | playlist 정보 저장 |
playlist_song | playlist와 playlist에 들어있는 곡 정보를 담고 있는 테이블 |
테이블간 관계
한개의 앨범에 여러 노래가 담길 수 있으니 album과 song의 관계는 1:n 관계로 간단하게 정했다. playlist와 song의 관계를 정할 때 조금 멈칫했다. 한개의 playlist에는 여러 song이 담길 수 있고, 한개의 노래가 여러 플레이리스트에 속할 수도 있다. m:n 관계인데, 표현 하기가 어려울 것 같아 playlist_song 테이블을 만들고 song과 playlist_song 1:n 관계로, playlist_song과 playlist는 n:1관계로 정리하였다.
테이블 | 관계 |
---|---|
album - song | 1:n |
song - playlist_song | 1:n |
playlist_song - playlist | n:1 |
공통 기능 설계
공통 변수
전역으로 사용할 수 있는 변수를 뽑아서 모아놓았다.
public class Constants {
public enum OrderBy{
ASC("ASC", "Asc Order"),
DESC("DESC", "Desc Order");
private final String value;
private final String reasonPhrase;
OrderBy(String value, String reasonPhrase) {
this.value = value;
this.reasonPhrase = reasonPhrase;
}
public String value() {
return this.value;
}
}
public static int ROW_PER_PAGE = 10;
}
예외처리
예외처리를 한곳에서 할 수 있도록 @ControllerAdvice
어노테이션을 붙여 ExceptionAdvice 클래스를 만들었다. 발생한 Exception의 처리를 한다. 예외처리를 한 곳에 몰아놓는 것과 발생한 자리에서 catch 한 후 처리하는 것 사이에 어떤게 나은지는 잘 모르겠다. 각 장단점이 있는 것 같은데, 실무에선 팀원들과 어떻게 할지 상의한 후 결정한다면 어느 방향을 사용하던 상관없을 것 같다.
@ControllerAdvice
@RestController
public class ExceptionAdvice {
@ExceptionHandler(BindException.class)
public ResponseEntity<?> requestParamValidationError(BindException exception) {
return bindResult(exception.getBindingResult());
}
...
}
Response body 정리
보통 API 설계할 때 공통 폼이 있다. 이번 과제를 하며 내 경우는 간단하게
{
"statusCode": ...,
"errorMsg":...(에러 발생한 경우),
"data": {
...
}
}
이런 식으로 정의했다.
공통으로 사용할 형식을 정의했기에 재사용하며 사용할 수 있는 클래스를 만들기로 했고, ResponseBody
클래스를 만들었다. 그리고 여기에 Builder패턴을 적용했다. 한번 사용해보고 싶기도 했고,
- 생성자 방식과 비교해선 필요한 파라미터만 골라서 넘길 수 있다는 장점이
- 지금 경우엔 해당되지 않지만, 같은 타입의 파라미터가 여러 개 존재할 때 파라미터의 순서를 헷갈릴 수도 있고, 필요한 파라미터에 따라 생성자를 여러 개 만들어야 한다는 수고로움이 있을 수 있다.
- setter방법 보단 좀 더 간단하게 사용할 수 있을것
- 생성자로 생성 후 세터를 또 호출
- (솔직히 이건 좀 억지같긴 하다...)
같았기 때문이다.
페이징 처리
페이징은 보통 쿼리 에서 limit
과 order by
를 이용해서 한다. 얼마전에 잠깐 jpa를 공부해 봤는데, jpa에서는 Paging
이라는 객체를 넘기면 알아서 해주는 것을 보고 비슷하게 적용해봐야겠다 생각했다. 그래서 Order
, Paging
클래스를 만들었고, 쿼리 실행 시 넘기는 파라미터 dto클래스에 조합해서 사용하는 방식으로 개발 했다. 좀 더 나은 방식으로 개발할 수 있었을것 같은데 조금 아쉽다.
API 설계
여기에 사용된 도메인은 이렇게 분류할 수 있을 것 같다.
- 앨범 관련
- album, song
- 플레이리스트 관련
- playlist, playlistSong
따라서 패키지도 album 패키지와 playlist 패키지로 나누었다.
공통
- 파라미터 검증에서 조건에 맞지 않으면 예외를 발생시킨다.
@Valid
어노테이션, 기타 다른 어노테이션
- 리턴 타입으로
ResponseEntity<?>
, 위에 언급한ResponseBody
를 사용했다. - Http 메소드와 HttpStatusCode를 상황에 맞게 알맞은 값을 사용하도록 노력했다.
기능 Http Method Http Status Code 생성 Post HttpStatus.CREATED 조회 Get HttpStatus.OK 수정 Put HttpStatus.CREATED 공통 헤더
헤더명 값 필수 여부 Content-Type application/json 필수 UserId {userId} 필수 공통 Response
필드 필드명 설명 statusCode 상태코드 요청에 대한 결과를 HttpStatus Code로 내려준다. errMsg 에러메시지 에러가 생길 경우 에러 메시지가 세팅된다. data 데이터 요청에 대한 결과 데이터.
1. album
- 앨범 관련 요청을 처리한다.
- /album, /search 등...
2. playlist
플레이리스트 관련 요청을 처리한다.
/api/playlist/*
Playlist 생성 API
POST /api/playlist
HTTP REQUEST
POST /api/playlist HTTP/1.1 Host: localhost:5000 Content-Type: application/json UserId: 1 { "playlistName":"new playlist" }
파라미터
Parameter Description 필수 여부 playlistName 생성할 playlist의 이름 필수 HTTP RESPONSE
HTTP/1.1 201 CREATED Content-Length: 224 Content-Type: application/json;charset=UTF-8 { "statusCode": 201, "data": { "playlist": { "userId": 1, "playlistName": "new playlist", "id": 16 } } }
Response 필드
필드 설명 data.playlist.id 생성된 플레이리스트의 아이디 data.playlist.playlistName 생성된 플레이리스트의 이름 data.playlist.userId 유저 아이디
Playlist 노래, 앨범 추가 API
노래 개별 추가
POST /api/playlist/{playlistId}/song
Path Variable
Path Variable Description 필수 여부 playlistId 플레이리스트 id 필수 HTTP REQUEST
POST /api/playlist/14/song HTTP/1.1 Host: localhost:5000 Content-Type: application/json UserId: 1 { "songId":101, "albumId":13 }
파라미터
Parameter Description 필수 여부 songId 내 플레이리스트에 추가할 노래의 id 필수 albumId 내 플레이리스트에 추가할 노래의 albumId 필수 HTTP RESPONSE
HTTP/1.1 201 CREATED Content-Length: 230 Content-Type: application/json;charset=UTF-8 { "statusCode": 201, "data": { "playlistSong": { "id": 154, "playlistId": 14, "songId": 101, "albumId": 13 } } }
Response 필드
필드 설명 data.playlistSong[].id 생성된 playlistSong의 아이디 data.playlistSong[].playlistId 노래가 추가된 playlist의 아이디 data.playlistSong[].songId 노래 아이디 data.playlistSong[].albumId 앨범 아이디 노래 앨범단위로 추가
POST /api/playlist/{playlistId}/album
Path Variable
Path Variable Description 필수 여부 playlistId 플레이리스트 id 필수 HTTP REQUEST
POST /api/playlist/14/album HTTP/1.1 Host: localhost:5000 Content-Type: application/json UserId: 1 {"albumId":14}
파라미터
Parameter Description 필수 여부 albumId 내 플레이리스트에 추가할 노래의 albumId 필수 HTTP RESPONSE
HTTP/1.1 201 CREATED Content-Length: 742 Content-Type: application/json;charset=UTF-8 { "statusCode": 201, "data": { "playlistSong": [ { "id": 155, "playlistId": 14, "songId": 111, "albumId": 14 }, { "id": 0, "playlistId": 14, "songId": 112, "albumId": 14 }, ... { "id": 165, "playlistId": 14, "songId": 121, "albumId": 14 } ] } }
Response 필드
필드 설명 data.playlistSong[].id 생성된 playlistSong의 아이디 data.playlistSong[].playlistId 노래가 추가된 playlist의 아이디 data.playlistSong[].songId 노래 아이디 data.playlistSong[].albumId 앨범 아이디 Playlist 목록 API
GET /api/playlist
HTTP REQUEST
GET /api/playlist HTTP/1.1 Host: localhost:5000 UserId: 1
HTTP RESPONSE
HTTP/1.1 200 OK Content-Length: 265 Content-Type: application/json;charset=UTF-8 { "statusCode": 200, "data": { "playlist": [ { "userId": 1, "playlistName": "abcde", "id": 14 }, { "userId": 1, "playlistName": "new playlist", "id": 16 } ] } }
Response 필드
필드 설명 data.playlist[].id playlist의 아이디 data.playlist[].playlistName playlist의 이름 data.playlist[].userId 사용자 id
Playlist 삭제 API
DELETE /api/playlist/{playlistId}
Path Variable
Path Variable Description 필수 여부 playlistId 플레이리스트 id 필수 HTTP REQUEST
DELETE /api/playlist/15 HTTP/1.1 Host: localhost:5000 UserId: 1 Content-Type: application/json
HTTP RESPONSE
HTTP/1.1 200 OK Content-Length: 148
컨벤션
- 카멜케이스
- ResponseBody 정의
- 쿼리 앞부분에 /* mapperName.queryId */ 형식으로 주석 추가
'experiences > 과제리뷰' 카테고리의 다른 글
Intro (0) | 2021.12.22 |
---|
- Total
- Today
- Yesterday