|
1 | | -# 프로젝트 마일스톤 & 요구사항 |
| 1 | +기본 요구사항 |
| 2 | +API 명세 |
| 3 | +이번 미션은 아래의 API 스펙과 비교하며 구현해보세요. |
2 | 4 |
|
3 | | -## 🚀 프로젝트 마일스톤 |
| 5 | +API 스펙 v1.1 |
| 6 | +API 스펙을 준수한다면, 아래의 프론트엔드 코드와 호환됩니다. |
4 | 7 |
|
5 | | -- RESTful API로 재설계 및 리팩토링 |
6 | | -- Swagger를 활용한 API 문서 자동화 |
7 | | -- 프론트엔드 연동 |
8 | | -- PaaS를 활용한 배포 |
| 8 | +정적 리소스 v1.1.4 |
| 9 | +소스 코드(참고용) v1.1.4 |
| 10 | +프론트엔드 소스 코드는 참고용으로만 활용하세요. 수정하여 활용하는 경우 이어지는 요구사항 또는 미션을 수행하는 데 어려움이 있을 수 있습니다. |
9 | 11 |
|
10 | | ---- |
| 12 | +데이터베이스 |
| 13 | +[ ] 아래와 같이 데이터베이스 환경을 설정하세요. |
| 14 | +데이터베이스: discodeit |
| 15 | +유저: discodeit_user |
| 16 | +패스워드: discodeit1234 |
| 17 | +[ ] ERD를 참고하여 DDL을 작성하고, 테이블을 생성하세요. |
| 18 | +작성한 DDL 파일은 /src/main/resources/schema.sql 경로에 포함하세요. 9tj7wyc7q-image.png |
| 19 | +PK: Primary Key |
| 20 | +UK: Unique Key |
| 21 | +NN: Not Null |
| 22 | +FK: Foreign Key |
| 23 | +ON DELETE CASCADE: 연관 엔티티 삭제 시 같이 삭제 |
| 24 | +ON DELETE SET NULL: 연관 엔티티 삭제 시 NULL로 변경 |
| 25 | +Spring Data JPA 적용하기 |
| 26 | +[ ] Spring Data JPA와 PostgreSQL을 위한 의존성을 추가하세요. |
| 27 | +[ ] 앞서 구성한 데이터베이스에 연결하기 위한 설정값을 application.yaml 파일에 작성하세요. |
| 28 | +[ ] 디버깅을 위해 SQL 로그와 관련된 설정값을 application.yaml 파일에 작성하세요. |
| 29 | +엔티티 정의하기 |
| 30 | +[ ] 클래스 다이어그램을 참고해 도메인 모델의 공통 속성을 추상 클래스로 정의하고 상속 관계를 구현하세요. |
11 | 31 |
|
12 | | -## 📋 기본 요구사항 |
| 32 | +이때 Serializable 인터페이스는 제외합니다. |
13 | 33 |
|
14 | | -1. **RESTful API 재설계** |
15 | | - - 스프린트 미션#4에서 구현한 API를 RESTful API로 다시 설계 |
16 | | - - API 스펙을 확인하고 본인이 설계한 API와 비교 |
17 | | - - `oasdiff`를 활용하면 API 비교가 수월함 |
18 | | - - 제공된 API 스펙에 맞추어 구현 (심화 요구사항 프론트엔드 연동을 위해 필수) |
| 34 | +패키지명: com.sprint.mission.discodeit.entity.base |
19 | 35 |
|
20 | | -2. **API 테스트** |
21 | | - - Postman을 활용하여 컨트롤러 테스트 |
22 | | - - 테스트 결과를 export하여 PR에 첨부 |
| 36 | +클래스 다이어그램 |
23 | 37 |
|
24 | | -3. **Swagger 기반 API 문서화** |
25 | | - - `springdoc-openapi` 활용 |
26 | | - - Swagger-UI를 통해 API 테스트 가능 |
| 38 | +[ ] JPA의 어노테이션을 활용해 createdAt, updatedAt 속성이 자동으로 설정되도록 구현하세요. |
27 | 39 |
|
28 | | ---- |
| 40 | +@CreatedDate, @LastModifiedDate |
| 41 | +[ ] 클래스 다이어그램을 참고해 클래스 참조 관계를 수정하세요. 필요한 경우 생성자, update 메소드를 수정할 수 있습니다. 단, 아직 JPA Entity와 관련된 |
| 42 | +어노테이션은 작성하지 마세요. |
29 | 43 |
|
30 | | -## ✨ 심화 요구사항 |
| 44 | +클래스 다이어그램 |
| 45 | +pq5iz92wt-image.png |
31 | 46 |
|
32 | | -1. **정적 리소스 서빙** |
33 | | - - 제공된 `fe_1.0.0.zip` 활용 |
34 | | - - API 스펙을 준수하면 프론트엔드와 정상 연동 |
| 47 | +화살표의 방향과 화살표 유무에 유의하세요. |
35 | 48 |
|
36 | | -2. **PaaS 배포 (Railway.app)** |
37 | | - - Railway.app 가입 및 GitHub 레포지토리 연결 |
38 | | - - `Settings > Network` 섹션에서 Generate Domain 버튼으로 도메인 생성 |
39 | | - - 생성된 도메인 접속 후 애플리케이션 테스트 |
| 49 | +[ ] ERD와 클래스 다이어그램을 토대로 연관관계 매핑 정보를 표로 정리해보세요.(이 내용은 PR에 첨부해주세요.) |
| 50 | +| A | B | 다중성 | 방향성 | 부모-자식 관계 | 연관관계의 주인 | |
| 51 | +| ------- | ------------- | ------ | ----------------------------------------- | ------------------------------ | ---------- | |
| 52 | +| User | UserStatus | 1:1 | 단방향 User → UserStatus | 부모: User, 자식: UserStatus | User | |
| 53 | +| User | ReadStatus | 1\:N | 단방향 ReadStatus → User | 부모: User, 자식: ReadStatus | ReadStatus | |
| 54 | +| User | Message | 1\:N | 단방향 Message → User (author) | 부모: User, 자식: Message | |
| 55 | +Message | |
| 56 | +| User | BinaryContent | 1:1 (Nullable) | 단방향 User → BinaryContent (profile) | 부모: User, 자식: |
| 57 | +BinaryContent | User | |
| 58 | +| Channel | ReadStatus | 1\:N | 단방향 ReadStatus → Channel | 부모: Channel, 자식: ReadStatus | |
| 59 | +ReadStatus | |
| 60 | +| Channel | Message | 1\:N | 단방향 Message → Channel | 부모: Channel, 자식: Message | Message | |
| 61 | +| Message | BinaryContent | 1\:N | 단방향 Message → BinaryContent (attachments) | 부모: Message, 자식: |
| 62 | +BinaryContent | Message | |
| 63 | + |
| 64 | +예시 |
| 65 | +엔티티 관계 다중성 방향성 부모-자식 관계 연관관계의 주인 |
| 66 | +A:B 1:N B→A 단방향 부모: A, 자식: B B |
| 67 | +[ ] JPA 주요 어노테이션을 활용해 ERD, 연관관계 매핑 정보를 도메인 모델에 반영해보세요. |
| 68 | +@Entity, @Table |
| 69 | +@Column, @Enumerated |
| 70 | +@OneToMany, @OneToOne, @ManyToOne |
| 71 | +@JoinColumn, @JoinTable |
| 72 | +[ ] ERD의 외래키 제약 조건과 연관관계 매핑 정보의 부모-자식 관계를 고려해 영속성 전이와 고아 객체를 정의하세요. |
| 73 | +cascade, orphanRemoval |
| 74 | +레포지토리와 서비스에 JPA 도입하기 |
| 75 | +[ ] 기존의 Repository 인터페이스를 JPARepository로 정의하고 쿼리메소드로 대체하세요. |
| 76 | +FileRepository와 JCFRepository 구현체는 삭제합니다. |
| 77 | +[ ] 영속성 컨텍스트의 특징에 맞추어 서비스 레이어를 수정해보세요. |
| 78 | +힌트: 트랜잭션, 영속성 전이, 변경 감지, 지연로딩 |
| 79 | +DTO 적극 도입하기 |
| 80 | +[ ] Entity를 Controller 까지 그대로 노출했을 때 발생할 수 있는 문제점에 대해 정리해보세요. DTO를 적극 도입했을 때 보일러플레이트 코드가 많아지지만, 그럼에도 |
| 81 | +불구하고 어떤 이점이 있는지 알 수 있을거에요.(이 내용은 PR에 첨부해주세요.) |
| 82 | +힌트 |
| 83 | +Entity와 API의 결합 |
| 84 | +프로덕션 환경에서는 성능을 고려해 OSIV를 false로 설정하는 경우가 대부분 |
| 85 | +양방향 연관관계 시 순환 참조 |
| 86 | +민감한 데이터 |
| 87 | +[ ] 다음의 클래스 다이어그램을 참고하여 DTO를 정의하세요. |
| 88 | +hd4c6g1of-image.png |
| 89 | + |
| 90 | +[ ] Entity를 DTO로 매핑하는 로직을 책임지는 Mapper 컴포넌트를 정의해 반복되는 코드를 줄여보세요. |
| 91 | + |
| 92 | +패키지명: com.sprint.mission.discodeit.mapper buo7cmjvp-image.png |
| 93 | +BinaryContent 저장 로직 고도화 |
| 94 | +데이터베이스에 이미지와 같은 파일을 저장하면 성능 상 불리한 점이 많습니다. 따라서 실제 바이너리 데이터는 별도의 공간에 저장하고, 데이터베이스에는 바이너리 데이터에 대한 메타 |
| 95 | +정보(파일명, 크기, 유형 등)만 저장하는 것이 좋습니다. |
| 96 | + |
| 97 | +[ ] BinaryContent 엔티티는 파일의 메타 정보(fileName, size, contentType)만 표현하도록 bytes 속성을 제거하세요. |
| 98 | + |
| 99 | +[ ] BinaryContent의 byte[] 데이터 저장을 담당하는 인터페이스를 설계하세요. |
| 100 | + |
| 101 | +저장 매체의 확장성(로컬 저장소, 원격 저장소)을 고려해 인터페이스부터 설계합니다. |
| 102 | + |
| 103 | +패키지명: com.sprint.mission.discodeit.storage |
| 104 | + |
| 105 | +클래스 다이어그램 |
| 106 | +nqt5zw2pk-image.png |
| 107 | + |
| 108 | +BinaryContentStorage |
| 109 | + |
| 110 | +바이너리 데이터의 저장/로드를 담당하는 컴포넌트입니다. |
| 111 | +UUID put(UUID, byte[]) |
| 112 | +UUID 키 정보를 바탕으로 byte[] 데이터를 저장합니다. |
| 113 | +UUID는 BinaryContent의 Id 입니다. |
| 114 | +InputStream get(UUID) |
| 115 | +키 정보를 바탕으로 byte[] 데이터를 읽어 InputStream 타입으로 반환합니다. |
| 116 | +UUID는 BinaryContent의 Id 입니다. |
| 117 | +ResponseEntity<?> download(BinaryContentDto) |
| 118 | +HTTP API로 다운로드 기능을 제공합니다. |
| 119 | +BinaryContentDto 정보를 바탕으로 파일을 다운로드할 수 있는 응답을 반환합니다. |
| 120 | +[ ] 서비스 레이어에서 기존에 BinaryContent를 저장하던 로직을 BinaryContentStorage를 활용하도록 리팩토링하세요. |
| 121 | + |
| 122 | +[ ] BinaryContentController에 파일을 다운로드하는 API를 추가하고, BinaryContentStorage에 로직을 위임하세요. |
| 123 | + |
| 124 | +엔드포인트: GET /api/binaryContents/{binaryContentId}/download |
| 125 | + |
| 126 | +요청 |
| 127 | + |
| 128 | +값: BinaryContentId |
| 129 | +방식: Query Parameter |
| 130 | +응답: ResponseEntity<?> |
| 131 | + |
| 132 | +클래스 다이어그램 |
| 133 | + |
| 134 | +5qwe2kqno-image.png |
| 135 | + |
| 136 | +[ ] 로컬 디스크 저장 방식으로 BinaryContentStorage 구현체를 구현하세요. |
| 137 | + |
| 138 | +클래스 다이어그램 |
| 139 | + |
| 140 | +skptrmm5p-image.png |
| 141 | + |
| 142 | +[ ] discodeit.storage.type 값이 local 인 경우에만 Bean으로 등록되어야 합니다. |
| 143 | + |
| 144 | +Path root |
| 145 | +로컬 디스크의 루트 경로입니다. |
| 146 | +discodeit.storage.local.root-path 설정값을 정의하고, 이 값을 통해 주입합니다. |
| 147 | +void init() |
| 148 | +루트 디렉토리를 초기화합니다. |
| 149 | +Bean이 생성되면 자동으로 호출되도록 합니다. |
| 150 | +Path resolvePath(UUID) |
| 151 | +파일의 실제 저장 위치에 대한 규칙을 정의합니다. |
| 152 | +파일 저장 위치 규칙 예시: {root}/{UUID} |
| 153 | +put, get 메소드에서 호출해 일관된 파일 경로 규칙을 유지합니다. |
| 154 | +ResponseEntity<Resource> donwload(BinaryContentDto) |
| 155 | +get 메소드를 통해 파일의 바이너리 데이터를 조회합니다. |
| 156 | +BinaryContentDto와 바이너리 데이터를 활용해 ResponseEntity<Resource> 응답을 생성 후 반환합니다. |
| 157 | +페이징과 정렬 |
| 158 | +[ ] 메시지 목록을 조회할 때 다음의 조건에 따라 페이지네이션 처리를 해보세요. |
| 159 | +50개씩 최근 메시지 순으로 조회합니다. |
| 160 | +총 메시지가 몇개인지 알 필요는 없습니다. |
| 161 | +[ ] 일관된 페이지네이션 응답을 위해 제네릭을 활용해 DTO로 구현하세요. |
| 162 | +패키지명: com.sprint.mission.discodeit.dto.response |
| 163 | + |
| 164 | +클래스 다이어그램 |
| 165 | +wj4q7nhn3-image.png |
| 166 | + |
| 167 | +content: 실제 데이터입니다. |
| 168 | + |
| 169 | +number: 페이지 번호입니다. |
| 170 | + |
| 171 | +size: 페이지의 크기입니다. |
| 172 | + |
| 173 | +totalElements: T 데이터의 총 갯수를 의미하며, null일 수 있습니다. |
| 174 | + |
| 175 | +[ ] Slice 또는 Page 객체로부터 DTO를 생성하는 Mapper를 구현하세요. |
| 176 | +패키지명: com.sprint.mission.discodeit.mapper |
| 177 | + |
| 178 | +x7qjncxm0-image.png |
| 179 | + |
| 180 | +확장성을 위해 제네릭 메소드로 구현하세요. |
0 commit comments