-
Notifications
You must be signed in to change notification settings - Fork 24
박인규 sprint3 #91
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
base: main
Are you sure you want to change the base?
The head ref may contain hidden characters: "\uBC15\uC778\uADDC-sprint3"
박인규 sprint3 #91
Conversation
ㄴ1. {break;} 등의 컨벤션, 줄바꿈, 들여쓰기 등 전반적인 문법 통일 ㄴ2. Channel entity가 MessageService 객체를 필드값으로 가지는 부분 수정. MessageList 컬렉션을 Channel객체의 UUID를 키값으로 갖는 Map 형태로 구조변경함. ㄴ3. 불필요한 호출, 메서드 선언 등 삭제 ㄴ4. 메서드 호출 시 넘기는 파라미터값을 필요최소로 조정함.예시 : (User user -> String user.getName()) ㄴ5. 부모 클래스에서 자식 클래스의 메서드를 참조하는 등 잘못된 상속관계 수정.
… 변경하여 ThreadSafe 구현
ㄴFileJavaApplication.java에서 구현 ㄴsrc/files/user.ser,channel.ser,messages.ser 파일로 저장 ㄴ각 컨트롤에서 int값이 아닌 값을 읽은 경우 다시 재입력 활성화하도록 변경.
ㄴFileJavaApplication.java에서 구현 ㄴsrc/files/user.ser,channel.ser,messages.ser 파일로 저장 ㄴ각 컨트롤에서 int값이 아닌 값을 읽은 경우 다시 재입력 활성화하도록 변경.
- Repository 정의하며 Message구조 전반적인 리팩터링
- SpringBoot 적용함 - @SpringBootApplicatin / @service / @repository 어노테이션 작성함 - ConfigurableApplicationContext getBean 활용하여 각 서비스 초기화 적용함 - 셋업,테스트 코드 이용하여 테스트완료
ㄴ 각 Entity에 @Getter 적용 ㄴ Basic*Service에 @RequidedArgsConstructor 적용
@SpringBootApplication | ||
public class DiscodeitApplication { | ||
static User setupUser(UserService userService) { | ||
UserCreateRequest userCreateRequest = new UserCreateRequest("인규","[email protected]","password"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 프로젝트에서 lombok을 사용하고 있는데, 기왕 롬복을 사용할거라면 인스턴스를 생성할때도 Builder 패턴을 사용하길 권장드려요.
이유는 new 생성자로 생성하는건 파라미터의 순서와 갯수를 매번 고려해야하고 읽는 사람이 가독성이 떨어지는 단점이 있어요.
반면에 Builder 패턴은 어떤 변수에 어떤 값이 들어갈지 바로 보이기 때문에 가독성이나 순서와 갯수를 고려하지 않아도 돼죠.
현대화된 언어들에서는Named Parameter
라는 개념으로 파라미터를 호출할때 지정하는 방법이 있는데, 자바에서는 아직 그게 없네요 ㅎㅎ 그래서 builder 패턴을 사용하는거에요.
참고
빌터패턴 : https://gwonbookcase.tistory.com/100?category=777301
Dart의 Named parameter : https://leftday.tistory.com/120
UUID userId, | ||
String username, | ||
String email, | ||
boolean online, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
온라인 유저를 찾을건지 오프라인 유저를 찾을건지도 조회할때 옵션으로 필요한가요?
import java.util.UUID; | ||
|
||
public record PrivateChannelCreateRequest( | ||
List<UUID> inChannelUsers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DTO에서 이미 Channel에 대한 거라는 의미가 포함되어 있어 필드에 채널을 의미하는 표현은 없어도 될 것 같습니다.
오히려 User가 아니라 User ID라고 표현을 명확하게 하는게 더 좋을 것 같습니다.
} | ||
|
||
static Channel setupChannel(ChannelService channelService) { | ||
Channel channel = channelService.create(ChannelType.PUBLIC, "공지", "공지 채널입니다."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이거 동작하는 코드 아니죠? 아직 안 지우신거죠?
private int msgNumber; | ||
private UUID id; | ||
private Long createdAt; | ||
private Long updatedAt; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
다른데는 Instant createdAt
이렇게 바꿔주셨는데, 일부는 아직 안 바꿔주신것 같아요. 확인 부탁드립니다!
void updateUserName(User user,String name); // 기존 유저 수정 | ||
void printAllUsers(); | ||
List<User> getUserslist(); // 유저 리스트 반환 | ||
User create(UserCreateRequest userCreateRequest, Optional<BinaryContentCreateRequest> portraitCreateRequest); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
파라미터로 Optional을 받는건 조금 어색한거 같아요. 일단 Optional을 파라미터로 받는건 안티패턴으로 알고 있어요. Optional은 반환값을 관리위해서 사용된 개념이어서요.
참고 : https://yeonyeon.tistory.com/224
질문에도 있던 사항인거 같아 같이 답을 드리면, 나중에 Rest API 형태로 구현할거라 생각이 되는데요. 그때 보통 Controller에서 Multipart 형태로 데이터를 받을거에요. 파일 자체가 크니까 Controller에서 local에 특정 경로(여러 저장 요청을 동시에 받을 수 있으므로 경로 timestamp로 경로 구분해서) 에 저장해두고 그 경로만 UserCreateRequestDTO
에 같이 넣는게 좋을거 같아요. 파일을 안 받은 경우에는 그 경로는 null로 할지 아니면 boolean으로 파일을 받았는지 아닌지 관리할지는 정하시면 될거 같아요.
그리고 Controller에서 finally 혹은 try-resources로 반드시 local에 저장해둔 파일 있으면 삭제까지 하구요.
그런데 요구사항에서 User랑 Message에서 BinaryContent를 의존하는걸로 봐서는 코드 내에서 파일 바이너리 값은 전체를 계속 다룰 생각인가 보내요. 그 바이너리 값도 UserCreateRequestDTO 안에 있는게 더 좋을거 같아요. 그게 더 직관적일거 같아요
if (nameExists) { | ||
throw new IllegalArgumentException("Username already exists"); | ||
} | ||
boolean emailExists = userRepository.findAll().stream() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
userName 중복은 exist로 존재 유무만 판단하는걸로 잘 하셨는데, 아래 Email 중복도 모든 유저정보를 가져와서 필터링 하는 로직으로 수정하면 성능에 문제가 될거에요.
다른 곳들도 비슷한게 있는데 코멘트 굳이 달진 않을게요.
public void setName(String name) { | ||
this.name = name; | ||
} | ||
public void update(String newUsername, String newEmail, String newPassword) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
들여쓰기 맞춰주세요. 탭 하나 더 들어가있는거 같아요.
|
||
@Override | ||
public Channel create(PrivateChannelCreateRequest request) { | ||
Channel channel = new Channel(ChannelType.PRIVATE, null, null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private 채널 생성할때 name이랑 description 어차피 안 받을건데, 매번 null 받게 하는게 적절한지 잘 모르겠네요.. 으흠.. 🤔
요구사항
기본
기본 요구사항
[x]Spring 프로젝트 초기화
[x]Spring Initializr를 통해 zip 파일을 다운로드하세요.
[x] 빌드 시스템은 Gradle - Groovy를 사용합니다.
[x] 언어는 Java 17를 사용합니다.
[x] Spring Boot의 버전은 3.4.0입니다.
[x] GroupId는 com.sprint.mission입니다.
[x] ArtifactId와 Name은 discodeit입니다.
[x] packaging 형식은 Jar입니다.
[x] Dependency를 추가합니다.
[x] Lombok
[x] Spring Web
[x] zip 파일을 압축해제하고 원래 진행 중이던 프로젝트에 붙여넣기하세요. 일부 파일은 덮어쓰기할 수 있습니다.
[x] application.properties 파일을 yaml 형식으로 변경하세요.
[x] DiscodeitApplication의 main 메서드를 실행하고 로그를 확인해보세요.
Bean 선언 및 테스트
[x] File*Repository 구현체를 Repository 인터페이스의 Bean으로 등록하세요.
[x] Basic*Service 구현체를 Service 인터페이스의 Bean으로 등록하세요.
[x] JavaApplication에서 테스트했던 코드를 DiscodeitApplication에서 테스트해보세요.
[x] JavaApplication의 main 메소드를 제외한 모든 메소드를 DiscodeitApplication 클래스로 복사하세요.
[x] JavaApplication의 main 메소드에서 Service를 초기화하는 코드를 Spring Context를 활용하여 대체하세요.
Spring 핵심 개념 이해하기
JavaApplication과 DiscodeitApplication에서 Service를 초기화하는 방식의 차이에 대해 다음의 키워드를 중심으로 정리해보세요.
IoC Container
Dependency Injection
Bean
이 내용은 PR에 첨부해주세요.
IoC Contatiner: 제어의 역전으로 이번 미션을 예로들어 기존엔 수동으로 개발자가 추가하던 의존성 주입을 스프링의 ioc컨테이너를 통해 객체의 생성과 의존성 주입을 관리합니다.
DI: java 방식은 new 를 통해 의존성을 직접 주입합니다. discodeit 방식은 자동으로 의존성을 주입하고 관련 어노테이션들을 지원합니다.
Bean: 스프링 ioc 컨테이너를 사용하지 않고 new를 통해 객체를 생성해 사용했으나 ioc를 이용한 discodeitApplication은 @componentscan을 통한 빈 자동스캔과 등록, getBean을 통해 사용할 빈을 가져옵니다.
** Lombok 적용 **
[x] 도메인 모델의 getter 메소드를 @Getter로 대체해보세요.
[x] Basic*Service의 생성자를 @requiredargsconstructor로 대체해보세요.
비즈니스 로직 고도화
다음의 기능 요구 사항을 구현하세요.
[x] 시간 타입 변경하기
[x] 시간을 다루는 필드의 타입은 Instant로 통일합니다.
새로운 도메인 추가하기
[x] 공통: 앞서 정의한 도메인 모델과 동일하게 공통 필드(id, createdAt, updatedAt)를 포함합니다.
[x] ReadStatus
사용자가 채널 별 마지막으로 메시지를 읽은 시간을 표현하는 도메인 모델입니다. 사용자별 각 채널에 읽지 않은 메시지를 확인하기 위해 활용합니다.
[x] UserStatus
사용자 별 마지막으로 확인된 접속 시간을 표현하는 도메인 모델입니다. 사용자의 온라인 상태를 확인하기 위해 활용합니다.
[x] 마지막 접속 시간을 기준으로 현재 로그인한 유저로 판단할 수 있는 메소드를 정의하세요.
[x] BinaryContent
이미지, 파일 등 바이너리 데이터를 표현하는 도메인 모델입니다. 사용자의 프로필 이미지, 메시지에 첨부된 파일을 저장하기 위해 활용합니다.
[x] 수정 불가능한 도메인 모델로 간주합니다. 따라서 updatedAt 필드는 정의하지 않습니다.
[x] User, Message 도메인 모델과의 의존 관계 방향성을 잘 고려하여 id 참조 필드를 추가하세요.
[x] 각 도메인 모델 별 레포지토리 인터페이스를 선언하세요.
DTO 활용하기
UserService 고도화
고도화
[ ] create
선택적으로 프로필 이미지를 같이 등록할 수 있습니다.
DTO를 활용해 파라미터를 그룹화합니다.
유저를 등록하기 위해 필요한 파라미터, 프로필 이미지를 등록하기 위해 필요한 파라미터 등
username과 email은 다른 유저와 같으면 안됩니다.
UserStatus를 같이 생성합니다.
find, findAll
DTO를 활용하여:
사용자의 온라인 상태 정보를 같이 포함하세요.
패스워드 정보는 제외하세요.
update
선택적으로 프로필 이미지를 대체할 수 있습니다.
DTO를 활용해 파라미터를 그룹화합니다.
수정 대상 객체의 id 파라미터, 수정할 값 파라미터
delete
관련된 도메인도 같이 삭제합니다.
BinaryContent(프로필), UserStatus
의존성
같은 레이어 간 의존성 주입은 순환 참조 방지를 위해 지양합니다. 다른 Service 대신 필요한 Repository 의존성을 주입해보세요.
AuthService 구현
login
username, password과 일치하는 유저가 있는지 확인합니다.
일치하는 유저가 있는 경우: 유저 정보 반환
일치하는 유저가 없는 경우: 예외 발생
DTO를 활용해 파라미터를 그룹화합니다.
의존성
같은 레이어 간 의존성 주입은 순환 참조 방지를 위해 지양합니다. 다른 Service 대신 필요한 Repository 의존성을 주입해보세요.
ChannelService 고도화
고도화
create
PRIVATE 채널과 PUBLIC 채널을 생성하는 메소드를 분리합니다.
분리된 각각의 메소드를 DTO를 활용해 파라미터를 그룹화합니다.
PRIVATE 채널을 생성할 때:
채널에 참여하는 User의 정보를 받아 User 별 ReadStatus 정보를 생성합니다.
name과 description 속성은 생략합니다.
PUBLIC 채널을 생성할 때에는 기존 로직을 유지합니다.
find
DTO를 활용하여:
해당 채널의 가장 최근 메시지의 시간 정보를 포함합니다.
PRIVATE 채널인 경우 참여한 User의 id 정보를 포함합니다.
findAll
DTO를 활용하여:
해당 채널의 가장 최근 메시지의 시간 정보를 포함합니다.
PRIVATE 채널인 경우 참여한 User의 id 정보를 포함합니다.
특정 User가 볼 수 있는 Channel 목록을 조회하도록 조회 조건을 추가하고, 메소드 명을 변경합니다. findAllByUserId
PUBLIC 채널 목록은 전체 조회합니다.
PRIVATE 채널은 조회한 User가 참여한 채널만 조회합니다.
update
DTO를 활용해 파라미터를 그룹화합니다.
수정 대상 객체의 id 파라미터, 수정할 값 파라미터
PRIVATE 채널은 수정할 수 없습니다.
delete
관련된 도메인도 같이 삭제합니다.
Message, ReadStatus
의존성
같은 레이어 간 의존성 주입은 순환 참조 방지를 위해 지양합니다. 다른 Service 대신 필요한 Repository 의존성을 주입해보세요.
MessageService 고도화
고도화
create
선택적으로 여러 개의 첨부파일을 같이 등록할 수 있습니다.
DTO를 활용해 파라미터를 그룹화합니다.
findAll
특정 Channel의 Message 목록을 조회하도록 조회 조건을 추가하고, 메소드 명을 변경합니다. findallByChannelId
update
DTO를 활용해 파라미터를 그룹화합니다.
수정 대상 객체의 id 파라미터, 수정할 값 파라미터
delete
관련된 도메인도 같이 삭제합니다.
첨부파일(BinaryContent)
의존성
같은 레이어 간 의존성 주입은 순환 참조 방지를 위해 지양합니다. 다른 Service 대신 필요한 Repository 의존성을 주입해보세요.
ReadStatusService 구현
create
DTO를 활용해 파라미터를 그룹화합니다.
관련된 Channel이나 User가 존재하지 않으면 예외를 발생시킵니다.
같은 Channel과 User와 관련된 객체가 이미 존재하면 예외를 발생시킵니다.
find
id로 조회합니다.
findAllByUserId
userId를 조건으로 조회합니다.
update
DTO를 활용해 파라미터를 그룹화합니다.
수정 대상 객체의 id 파라미터, 수정할 값 파라미터
delete
id로 삭제합니다.
의존성
같은 레이어 간 의존성 주입은 순환 참조 방지를 위해 지양합니다. 다른 Service 대신 필요한 Repository 의존성을 주입해보세요.
UserStatusService 고도화
create
DTO를 활용해 파라미터를 그룹화합니다.
관련된 User가 존재하지 않으면 예외를 발생시킵니다.
같은 User와 관련된 객체가 이미 존재하면 예외를 발생시킵니다.
find
id로 조회합니다.
findAll
모든 객체를 조회합니다.
update
DTO를 활용해 파라미터를 그룹화합니다.
수정 대상 객체의 id 파라미터, 수정할 값 파라미터
updateByUserId
userId 로 특정 User의 객체를 업데이트합니다.
delete
id로 삭제합니다.
의존성
같은 레이어 간 의존성 주입은 순환 참조 방지를 위해 지양합니다. 다른 Service 대신 필요한 Repository 의존성을 주입해보세요.
BinaryContentService 구현
create
DTO를 활용해 파라미터를 그룹화합니다.
find
id로 조회합니다.
findAllByIdIn
id 목록으로 조회합니다.
delete
id로 삭제합니다.
의존성
같은 레이어 간 의존성 주입은 순환 참조 방지를 위해 지양합니다. 다른 Service 대신 필요한 Repository 의존성을 주입해보세요.
새로운 도메인 Repository 구현체 구현
지금까지 인터페이스로 설계한 각각의 Repository를 JCF, File로 각각 구현하세요.
심화
Bean 다루기
Repository 구현체 중에 어떤 구현체를 Bean으로 등록할지 Java 코드의 변경 없이 application.yaml 설정 값을 통해 제어해보세요.
미완성입니다. 추후 추가작업하여 보충 제출하겠습니다