|
1 |
| -# 스프린트 미션 1 |
2 |
| -디스코드 서비스 만들기 |
3 |
| - |
4 |
| -## 🚀 목표 |
5 |
| - |
6 |
| -1. Git과 GitHub을 통해 프로젝트를 관리할 수 있다. |
7 |
| -2. 채팅 서비스의 도메인 모델을 설계하고, Java로 구현할 수 있다. |
8 |
| -3. 인터페이스를 설계하고 구현체를 구현할 수 있다. |
9 |
| -4. 싱글톤 패턴을 구현할 수 있다. |
10 |
| -5. Java Collections Framework에 데이터를 생성/수정/삭제할 수 있다. |
11 |
| -6. Stream API를 통해 JCF의 데이터를 조회할 수 있다. |
12 |
| -7. [심화] 모듈 간 의존 관계를 이해하고 팩토리 패턴을 활용해 의존성을 관리할 수 있다. |
13 |
| - |
14 |
| -## 프로젝트 마일스톤 |
15 |
| - |
16 |
| -- 프로젝트 초기화 |
17 |
| -- 도메인 모델 구현 |
18 |
| -- 서비스 인터페이스 설계 및 구현 |
19 |
| - - 각 도메인 모델별 CRUD |
20 |
| - - JCF 메모리 기반 |
21 |
| -- 의존성 주입 |
22 |
| - |
23 |
| -## ⭐ 요구사항 |
24 |
| - |
25 |
| -### 기본 요구사항 |
26 |
| - |
27 |
| -**1️⃣ 프로젝트 초기화** |
28 |
| - |
29 |
| -- [x] IntelliJ를 통해 다음의 조건으로 Java 프로젝트를 생성합니다. |
30 |
| - - [x] IntelliJ에서 제공하는 프로젝트 템플릿 중 Java를 선택합니다. |
31 |
| - - [x] 프로젝트의 경로는 스프린트 미션 리포지토리의 경로와 같게 설정합니다. |
32 |
| - - [x] Create Git Repository 옵션은 체크하지 않습니다. |
33 |
| - - [x] Build system은 Gradle을 사용합니다. Gradle DSL은 Groovy를 사용합니다. |
34 |
| - - [x] JDK 17을 선택합니다. |
35 |
| - - [x] GroupId는 com.sprint.mission로 설정합니다. |
36 |
| - - [x] ArtifactId는 수정하지 않습니다. |
37 |
| - - [x] .gitignore에 IntelliJ와 관련된 파일이 형상관리 되지 않도록 .idea디렉토리를 추가합니다. |
38 |
| - |
39 |
| -**2️⃣ 도메인 모델링** |
40 |
| - |
41 |
| -- [ ] 디스코드 서비스를 활용해보면서 각 도메인 모델에 필요한 정보를 도출하고, Java Class로 구현하세요. |
42 |
| - - [x] 패키지명: `com.sprint.mission.discodeit.entity` |
43 |
| - - [ ] 도메인 모델 정의 |
44 |
| - - [x] 공통 |
45 |
| - - [x] `id`: 객체를 식별하기 위한 id로 UUID 타입으로 선언합니다. |
46 |
| - - [x] `createdAt`, `updatedAt`: 각각 객체의 생성, 수정 시간을 유닉스 타임스탬프로 나타내기 위한 필드로 Long 타입으로 선언합니다. |
47 |
| - -[ ] User |
48 |
| - - [ ] Channel |
49 |
| - - [ ] Message |
50 |
| - -[ ] 생성자 |
51 |
| - - [x] `id`는 생성자에서 초기화하세요. |
52 |
| - - [x] `createdAt`는 생성자에서 초기화하세요. |
53 |
| - - [x] `id`, `createdAt`, `updatedAt`을 제외한 필드는 생성자의 파라미터를 통해 초기화하세요. |
54 |
| - - [ ] 메소드 |
55 |
| - - [ ] 각 필드를 반환하는 `Getter` 함수를 정의하세요. |
56 |
| - - [ ] 필드를 수정하는 `update` 함수를 정의하세요. |
57 |
| - |
58 |
| -**3️⃣ 서비스 설계 및 구현** |
59 |
| - |
60 |
| -- [x] 도메인 모델 별 CRUD(생성, 읽기, 모두 읽기, 수정, 삭제) 기능을 인터페이스로 선언하세요. |
61 |
| - - [x] 인터페이스 패키지명: `com.sprint.mission.discodeit.service` |
62 |
| - - [x] 인터페이스 네이밍 규칙: `[도메인 모델 이름]Service` |
63 |
| -- [x] 다음의 조건을 만족하는 서비스 인터페이스의 구현체를 작성하세요. |
64 |
| - - [x] 클래스 패키지명: `com.sprint.mission.discodeit.service.jcf` |
65 |
| - - [x] 클래스 네이밍 규칙: `JCF[인터페이스 이름]` |
66 |
| - - [x] Java Collections Framework를 활용하여 데이터를 저장할 수 있는 필드(data)를 final로 선언하고 생성자에서 초기화하세요. |
67 |
| - - [x] `data` 필드를 활용해 생성, 조회, 수정, 삭제하는 메소드를 구현하세요. |
68 |
| - |
69 |
| -**4️⃣ 메인 클래스 구현** |
70 |
| - |
71 |
| -- [ ] 메인 메소드가 선언된 `JavaApplication` 클래스를 선언하고, 도메인 별 서비스 구현체를 테스트해보세요. |
72 |
| - - [ ] 등록 |
73 |
| - - [ ] 조회(단건, 다건) |
74 |
| - - [ ] 수정 |
75 |
| - - [ ] 수정된 데이터 조회 |
76 |
| - - [ ] 삭제 |
77 |
| - - [ ] 조회를 통해 삭제되었는지 확인 |
78 |
| - |
79 |
| -**5️⃣기본 요구사항 커밋 태그** |
80 |
| - |
81 |
| -- [ ] 여기까지 진행 후 반드시 커밋해주세요. 그리고 `sprint1-basic` 태그를 생성해주세요. |
82 |
| - |
83 |
| -### 심화 요구사항 |
84 |
| - |
85 |
| -**서비스 간 의존성 주입** |
86 |
| - |
87 |
| -- [ ] 도메인 모델 간 관계를 고려해서 검증하는 로직을 추가하고, 테스트해보세요. |
88 |
| - - 힌트: Message를 생성할 때 연관된 도메인 모델 데이터 확인하기 |
89 |
| - |
90 |
| -## 📝 기능 명세서 |
91 |
| - |
92 |
| -### 🚶 User |
93 |
| - |
94 |
| -- 새로운 고객이 회원가입을 통해 애플리케이션의 사용자가 된다. |
95 |
| -- 유저는 자신의 정보를 수정할 수 있다. |
96 |
| -- 유저는 해당 서비스를 탈퇴할 수 있다. |
97 |
| -- 유저는 새로운 채널을 생성할 수 있다. |
98 |
| -- 유저는 생성한 채널에 다른 사용자를 초대할 수 있다. |
99 |
| -- 유저는 채널에서 메시지를 전송할 수 있다. |
100 |
| -- 유저는 채널에서 전송한 메시지를 수정할 수 있다 |
101 |
| -- 유저는 채널에 전송한 메세지를 삭제할 수 있다. |
102 |
| - |
103 |
| -### ✉️ Message |
104 |
| - |
105 |
| -- 메세지를 생성할 수 있다. |
106 |
| -- 메시지를 수정할 수 있다. |
107 |
| -- 메시지를 삭제할 수 있다. |
108 |
| -- 모든 메시지를 조회할 수 있다. |
109 |
| -- 특정 메시지를 조회할 수 있다. |
110 |
| - |
111 |
| -### 🚪 Channel |
112 |
| - |
113 |
| -- 채널을 생성한다. |
114 |
| -- 채널을 삭제한다. |
115 |
| -- 메시지를 관리한다. |
116 |
| -- 유저를 관리한다. |
117 |
| - |
118 |
| - |
119 |
| -### 모호한 부분 결정 |
120 |
| - |
121 |
| -`TODO` |
122 |
| - |
123 |
| -- 생성 날짜와 수정 날짜는 우리가 아는 시간으로 저장하지 않고 `Long` 타입으로 유닉스 타임 스태프로 저장하고 있는다. |
124 |
| - |
125 |
| -- 이에 이 정보를 반환해줄 때 long 타입으로 반환해서 사용하는것은 곳곳에 변환 구현 코드를 짜야한다. 이에 한 곳에서 변환하는 코드를 관리하고 싶다. |
126 |
| - |
127 |
| -- 이를 관리하는 컨버터 ? 또는 수정할 때 저장 로직을 한 곳에서 관리? |
| 1 | +간단하게 얼른 하고 끝내버리자. |
| 2 | +1. 서비스 구현체를 구현하라. -> 기존 유저 서비스의 구현 |
| 3 | + |
| 4 | +## 로직 생각해보기 |
| 5 | +객체 직렬화를 사용해서 할 경우, 바이너리로 저장된다. 수정을 하고 싶은 경우 특정 부분만 삭제하거나, 수정하는 것이 어렵다 |
| 6 | +=> 모든 저장 내용을 읽어들이고 파일을 새롭게 쓰는 방법이 쉬운 방법이다. |
| 7 | + |
| 8 | +성능적으로 보면, 매우 안좋다고 볼 수 있다. 만약 파일에 저장된 데이터가 매우 많을 경우를 생각하면 읽고 새롭게 쓰고 하면 성능저하가 당연하다. |
| 9 | + |
| 10 | +데이터가 많을 경우 데이터베이스를 이용하는 것이 좋다. |
| 11 | + |
| 12 | +요구사항 |
| 13 | +기본 요구사항 |
| 14 | +File IO를 통한 데이터 영속화 |
| 15 | +- [ ] 다음의 조건을 만족하는 서비스 인터페이스의 구현체를 작성하세요. |
| 16 | + |
| 17 | +- [ ] 클래스 패키지명: com.sprint.mission.discodeit.service.file |
| 18 | + |
| 19 | +- [ ] 클래스 네이밍 규칙: File[인터페이스 이름] |
| 20 | + |
| 21 | +- [ ] JCF 대신 FileIO와 객체 직렬화를 활용해 메소드를 구현하세요. |
| 22 | + |
| 23 | +객체 직렬화/역직렬화 가이드 |
| 24 | + |
| 25 | +- [ ] Application에서 서비스 구현체를 File*Service로 바꾸어 테스트해보세요. |
| 26 | + |
| 27 | +서비스 구현체 분석 |
| 28 | +- [ ] JCF*Service 구현체와 File*Service 구현체를 비교하여 공통점과 차이점을 발견해보세요. |
| 29 | +- [ ] "비즈니스 로직"과 관련된 코드를 식별해보세요. |
| 30 | +- [ ] "저장 로직"과 관련된 코드를 식별해보세요. |
| 31 | +레포지토리 설계 및 구현 |
| 32 | +- [ ] "저장 로직"과 관련된 기능을 도메인 모델 별 인터페이스로 선언하세요. |
| 33 | +- [ ] 인터페이스 패키지명: com.sprint.mission.discodeit.repository |
| 34 | +- [ ] 인터페이스 네이밍 규칙: [도메인 모델 이름]Repository |
| 35 | +- [ ] 다음의 조건을 만족하는 레포지토리 인터페이스의 구현체를 작성하세요. |
| 36 | +- [ ] 클래스 패키지명: com.sprint.mission.discodeit.repository.jcf |
| 37 | +- [ ] 클래스 네이밍 규칙: JCF[인터페이스 이름] |
| 38 | +- [ ] 기존에 구현한 JCF*Service 구현체의 "저장 로직"과 관련된 코드를 참고하여 구현하세요. |
| 39 | +- [ ] 다음의 조건을 만족하는 레포지토리 인터페이스의 구현체를 작성하세요. |
| 40 | +- [ ] 클래스 패키지명: com.sprint.mission.discodeit.repository.file |
| 41 | +- [ ] 클래스 네이밍 규칙: File[인터페이스 이름] |
| 42 | +- [ ] 기존에 구현한 File*Service 구현체의 "저장 로직"과 관련된 코드를 참고하여 구현하세요. |
| 43 | +심화 요구 사항 |
| 44 | +관심사 분리를 통한 레이어 간 의존성 주입 |
| 45 | +- [ ] 다음의 조건을 만족하는 서비스 인터페이스의 구현체를 작성하세요. |
| 46 | +- [ ] 클래스 패키지명: com.sprint.mission.discodeit.service.basic |
| 47 | +- [ ] 클래스 네이밍 규칙: Basic[인터페이스 이름] |
| 48 | +- [ ] 기존에 구현한 서비스 구현체의 "비즈니스 로직"과 관련된 코드를 참고하여 구현하세요. |
| 49 | +- [ ] 필요한 Repository 인터페이스를 필드로 선언하고 생성자를 통해 초기화하세요. |
| 50 | +- [ ] "저장 로직"은 Repository 인터페이스 필드를 활용하세요. (직접 구현하지 마세요.) |
| 51 | +- [ ] Basic*Service 구현체를 활용하여 테스트해보세요. |
| 52 | +코드 템플릿 |
| 53 | + |
| 54 | +```java |
| 55 | + |
| 56 | + |
| 57 | +public class JavaApplication { |
| 58 | + static User setupUser(UserService userService) { |
| 59 | + User user = userService .create( "woody", "[email protected]", "woody1234"); |
| 60 | + return user; |
| 61 | + } |
| 62 | + |
| 63 | + static Channel setupChannel(ChannelService channelService) { |
| 64 | + Channel channel = channelService.create(ChannelType.PUBLIC, "공지", "공지 채널입니다."); |
| 65 | + return channel; |
| 66 | + } |
| 67 | + |
| 68 | + static void messageCreateTest(MessageService messageService, Channel channel, User author) { |
| 69 | + Message message = messageService.create("안녕하세요.", channel.getId(), author.getId()); |
| 70 | + System.out.println("메시지 생성: " + message.getId()); |
| 71 | + } |
| 72 | + |
| 73 | + public static void main(String[] args) { |
| 74 | + // 서비스 초기화 |
| 75 | + // TODO Basic*Service 구현체를 초기화하세요. |
| 76 | + UserService userService; |
| 77 | + ChannelService channelService; |
| 78 | + MessageService messageService; |
| 79 | + |
| 80 | + // 셋업 |
| 81 | + User user = setupUser(userService); |
| 82 | + Channel channel = setupChannel(channelService); |
| 83 | + // 테스트 |
| 84 | + messageCreateTest(messageService, channel, user); |
| 85 | + } |
| 86 | +} |
| 87 | +``` |
| 88 | + |
| 89 | +- [ ] JCF*Repository 구현체를 활용하여 테스트해보세요. |
| 90 | + |
| 91 | +- [ ] File*Repository 구현체를 활용하여 테스트해보세요. |
| 92 | + |
| 93 | +- [ ] 이전에 작성했던 코드(JCF*Service 또는 File*Service)와 비교해 어떤 차이가 있는지 정리해보세요. |
0 commit comments