Skip to content

Commit f522cff

Browse files
authored
Merge pull request #128 from jaewoo9797/part1-백재우-sprint2
[백재우] sprint2
2 parents 1fb9974 + 7c93044 commit f522cff

File tree

58 files changed

+1000
-341
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1000
-341
lines changed

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,6 @@ fabric.properties
117117
# End of https://www.toptal.com/developers/gitignore/api/intellij
118118

119119
/codeit-bootcamp-spring/1-sprint-mission/.idea
120-
.idea
120+
.idea
121+
/.idea/
122+
/codeit-bootcamp-spring/.idea/

codeit-bootcamp-spring/1-sprint-mission/.gitignore

+6-1
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,9 @@ bin/
3939
.vscode/
4040

4141
### Mac OS ###
42-
.DS_Store
42+
.DS_Store
43+
44+
45+
/build
46+
/build/test-results/
47+
/build/reports/
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,93 @@
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)와 비교해 어떤 차이가 있는지 정리해보세요.
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,40 @@
11
package com.sprint.mission.discodeit;
22

3+
import com.sprint.mission.discodeit.config.factory.ApplicationFileFactory;
4+
import com.sprint.mission.discodeit.entity.channel.dto.ChannelResponse;
5+
import com.sprint.mission.discodeit.entity.channel.dto.CreateNewChannelRequest;
6+
import com.sprint.mission.discodeit.entity.message.dto.DirectMessageInfoResponse;
7+
import com.sprint.mission.discodeit.entity.message.dto.SendDirectMessageRequest;
8+
import com.sprint.mission.discodeit.entity.user.dto.RegisterUserRequest;
9+
import com.sprint.mission.discodeit.entity.user.dto.UserInfoResponse;
10+
import java.io.IOException;
11+
import java.util.UUID;
12+
313
public class JavaApplication {
14+
private static final ApplicationFileFactory app = ApplicationFileFactory.getInstance();
15+
16+
public static void main(String[] args) throws IOException {
17+
UserInfoResponse user = registerUser();
18+
ChannelResponse channelResponse = registerChannel(user.uuid(), "스프링부트_1기");
19+
UserInfoResponse receiveMessageUserTemp = registerUser();
20+
DirectMessageInfoResponse sendDirectMessage = sendDirectMessage(user.uuid(), receiveMessageUserTemp.uuid(), "안녕하세요");
21+
}
422

5-
public static void main(String[] args) {
23+
private static UserInfoResponse registerUser() {
24+
var userService = app.getUserService();
25+
var registerRequest = new RegisterUserRequest("홍길동");
26+
return userService.register(registerRequest);
27+
}
28+
29+
private static ChannelResponse registerChannel(UUID userId, String channelName) {
30+
var channelService = app.getChannelService();
31+
var channelCreateRequest = new CreateNewChannelRequest(userId, channelName);
32+
return channelService.createChannelOrThrow(channelCreateRequest);
33+
}
634

35+
private static DirectMessageInfoResponse sendDirectMessage(UUID sendUserId, UUID receiveUserId, String message) {
36+
var directMessageService = app.getDirectMessageService();
37+
var sendDirectMessageRequest = new SendDirectMessageRequest(sendUserId, receiveUserId, message);
38+
return directMessageService.sendMessage(sendDirectMessageRequest);
739
}
840
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.sprint.mission.discodeit.common.error;
22

3-
public enum ErrorMessage {
3+
public enum ErrorCode {
44

55
/**
66
* 1000 ~ 1999 User 관련 에러
@@ -9,7 +9,7 @@ public enum ErrorMessage {
99

1010
USER_NAME_NULL(1_001, "유저의 이름은 반드시 존재해야합니다."),
1111

12-
NAME_LENGTH_ERROR_MESSAGE(1_002, "유저 이름의 길이 제한을 확인해주세요."),
12+
USER_NAME_LENGTH_OUT_OF_RANGE(1_002, "유저 이름의 길이 제한을 확인해주세요."),
1313

1414
USER_NOT_PARTICIPATED_CHANNEL(1_003, "유저가 참여하지 않은 방입니다."),
1515

@@ -21,16 +21,20 @@ public enum ErrorMessage {
2121
;
2222

2323
private final int errorCode;
24-
private final String message;
24+
private final String errorMessage;
2525

2626

27-
ErrorMessage(int errorCode, String message) {
27+
ErrorCode(int errorCode, String message) {
2828
this.errorCode = errorCode;
29-
this.message = message;
29+
this.errorMessage = message;
3030
}
3131

32-
public String getMessage() {
33-
return message;
32+
public int getErrorCode() {
33+
return errorCode;
34+
}
35+
36+
public String getErrorMessage() {
37+
return errorMessage;
3438
}
3539

3640
}

codeit-bootcamp-spring/1-sprint-mission/src/main/java/com/sprint/mission/discodeit/common/error/channel/ChannelException.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.sprint.mission.discodeit.common.error.channel;
22

3-
import com.sprint.mission.discodeit.common.error.ErrorMessage;
3+
import com.sprint.mission.discodeit.common.error.ErrorCode;
44
import java.util.UUID;
55

66
public class ChannelException extends RuntimeException {
@@ -13,28 +13,28 @@ private ChannelException(String message, Throwable cause) {
1313
super(message, cause);
1414
}
1515

16-
public static ChannelException of(ErrorMessage message) {
17-
return new ChannelException(message.getMessage());
16+
public static ChannelException of(ErrorCode message) {
17+
return new ChannelException(message.getErrorMessage());
1818
}
1919

20-
public static ChannelException ofErrorMessageAndNotExistChannelId(ErrorMessage message, UUID causeInputParameter) {
20+
public static ChannelException ofNotFound(ErrorCode message, UUID causeInputParameter) {
2121
var format =
2222
String.format(
2323
"%s : input Channel Id = %s",
24-
message.getMessage(),
24+
message.getErrorMessage(),
2525
causeInputParameter.toString()
2626
);
2727

2828
return new ChannelException(format);
2929
}
3030

31-
public static ChannelException ofErrorMessageAndCreatorName(
32-
ErrorMessage message,
31+
public static ChannelException ofNotCreatorName(
32+
ErrorCode message,
3333
String creatorName
3434
) {
3535
var format = String.format(
3636
"%s : 채널 생성자 %s 가 아닙니다.",
37-
message.getMessage(), creatorName
37+
message.getErrorMessage(), creatorName
3838
);
3939

4040
return new ChannelException(format);

0 commit comments

Comments
 (0)