Skip to content

[박진솔] sprint3 #90

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

Open
wants to merge 1 commit into
base: 박진솔
Choose a base branch
from

Conversation

JinsolPark
Copy link
Collaborator

@JinsolPark JinsolPark commented May 6, 2025

Spring 프로젝트 초기화

  • Spring Initializr를 통해 zip 파일을 다운로드하세요.
  • 빌드 시스템은 Gradle - Groovy를 사용합니다.
  • 언어는 Java 17를 사용합니다.
  • Spring Boot의 버전은 3.4.0입니다.
  • GroupId는 com.sprint.mission입니다.
  • ArtifactId와 Name은 discodeit입니다.
  • packaging 형식은 Jar입니다
  • Dependency를 추가합니다.
    • Lombok
    • Spring Web
  • zip 파일을 압축해제하고 원래 진행 중이던 프로젝트에 붙여넣기하세요. 일부 파일은 덮어쓰기할 수 있습니다.
  • application.properties 파일을 yaml 형식으로 변경하세요.
  • DiscodeitApplication의 main 메서드를 실행하고 로그를 확인해보세요.

Bean 선언 및 테스트

  • File*Repository 구현체를 Repository 인터페이스의 Bean으로 등록하세요.
  • Basic*Service 구현체를 Service 인터페이스의 Bean으로 등록하세요.
  • JavaApplication에서 테스트했던 코드를 DiscodeitApplication에서 테스트해보세요.
  • JavaApplication 의 main 메소드를 제외한 모든 메소드를 DiscodeitApplication클래스로 복사하세요.
  • JavaApplication의 main 메소드에서 Service를 초기화하는 코드를 Spring Context를 활용하여 대체하세요.

추가 기능 요구사항

시간 타입 변경하기

  • 시간을 다루는 필드의 타입은 Instant로 통일합니다.
  • 기존에 사용하던 Long보다 가독성이 뛰어나며, 시간대(Time Zone) 변환과 정밀한 시간 연산이 가능해 확장성이 높습니다.

새로운 도메인 추가하기

  • 도메인 모델 간 참조 관계를 참고하세요.

8y8gqa7me-image.png

  • 공통: 앞서 정의한 도메인 모델과 동일하게 공통 필드(id, createdAt, updatedAt)를 포함합니다.

  • ReadStatus

    • 사용자가 채널 별 마지막으로 메시지를 읽은 시간을 표현하는 도메인 모델입니다. 사용자별 각 채널에 읽지 않은 메시지를 확인하기 위해 활용합니다.
  • UserStatus

    • 사용자 별 마지막으로 확인된 접속 시간을 표현하는 도메인 모델입니다. 사용자의 온라인 상태를 확인하기 위해 활용합니다.
    • 마지막 접속 시간을 기준으로 현재 로그인한 유저로 판단할 수 있는 메소드를 정의하세요.
    • 마지막 접속 시간이 현재 시간으로부터 5분 이내이면 현재 접속 중인 유저로 간주합니다.
  • BinaryContent

    • 이미지, 파일 등 바이너리 데이터를 표현하는 도메인 모델입니다. 사용자의 프로필 이미지, 메시지에 첨부된 파일을 저장하기 위해 활용합니다.
    • 수정 불가능한 도메인 모델로 간주합니다. 따라서 updatedAt 필드는 정의하지 않습니다.
    • User, Message 도메인 모델과의 의존 관계 방향성을 잘 고려하여 id 참조 필드를 추가하세요.
  • 각 도메인 모델 별 레포지토리 인터페이스를 선언하세요.

    • 레포지토리 구현체(File, JCF)는 아직 구현하지 마세요. 이어지는 서비스 고도화 요구사항에 따라 레포지토리 인터페이스에 메소드가 추가될 수 있어요.

DTO 활용하기

  • DTO란?

UserService 고도화

고도화

  • create

    • 선택적으로 프로필 이미지를 같이 등록할 수 있습니다.
    • DTO를 활용해 파라미터를 그룹화합니다.
    • 유저를 등록하기 위해 필요한 파라미터, 프로필 이미지를 등록하기 위해 필요한 파라미터 등
    • username과 email은 다른 유저와 같으면 안됩니다.
    • UserStatus를 같이 생성합니다.
  • find, findAll

  • DTO를 활용하여:

    • 사용자의 온라인 상태 정보를 같이 포함하세요.
    • 패스워드 정보는 제외하세요.
  • update

    • 선택적으로 프로필 이미지를 대체할 수 있습니다.
    • DTO를 활용해 파라미터를 그룹화합니다.
    • 수정 대상 객체의 id 파라미터, 수정할 값 파라미터
  • delete

    • 관련된 도메인도 같이 삭제합니다.
    • BinaryContent(프로필), UserStatus
    • 의존성
    • 같은 레이어 간 의존성 주입은 순환 참조 방지를 위해 지양합니다. 다른 Service 대신 필요한 Repository 의존성을 주입해보세요.

jv2bxgd12-image.png

AuthService 구현

  • login
    • username, password과 일치하는 유저가 있는지 확인합니다.
    • 일치하는 유저가 있는 경우: 유저 정보 반환
    • 일치하는 유저가 없는 경우: 예외 발생
    • DTO를 활용해 파라미터를 그룹화합니다.
    • 의존성
    • 같은 레이어 간 의존성 주입은 순환 참조 방지를 위해 지양합니다. 다른 Service 대신 필요한 Repository 의존성을 주입해보세요.

usf34n0k1-image.png

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 의존성을 주입해보세요.

5f221mj11-image.png

MessageService 고도화

고도화

  • create

    • 선택적으로 여러 개의 첨부파일을 같이 등록할 수 있습니다.
    • DTO를 활용해 파라미터를 그룹화합니다.
  • findAll

    • 특정 Channel의 Message 목록을 조회하도록 조회 조건을 추가하고, 메소드 명을 변경합니다.
    • findallByChannelId
  • update

    • DTO를 활용해 파라미터를 그룹화합니다.
    • 수정 대상 객체의 id 파라미터, 수정할 값 파라미터
  • delete

    • 관련된 도메인도 같이 삭제합니다.
    • 첨부파일(BinaryContent)
    • 의존성
    • 같은 레이어 간 의존성 주입은 순환 참조 방지를 위해 지양합니다. 다른 Service 대신 필요한 Repository 의존성을 주입해보세요.

3lpcv58wo-image.png

ReadStatusService 구현

  • create

    • DTO를 활용해 파라미터를 그룹화합니다.
    • 관련된 Channel이나 User가 존재하지 않으면 예외를 발생시킵니다.
    • 같은 Channel과 User와 관련된 객체가 이미 존재하면 예외를 발생시킵니다.
  • find

    • id로 조회합니다.
  • findAllByUserId

    • userId를 조건으로 조회합니다.
  • update

    • DTO를 활용해 파라미터를 그룹화합니다.
    • 수정 대상 객체의 id 파라미터, 수정할 값 파라미터
  • delete

    • id로 삭제합니다.
    • 의존성
    • 같은 레이어 간 의존성 주입은 순환 참조 방지를 위해 지양합니다. 다른 Service 대신 필요한 Repository 의존성을 주입해보세요.

dvbyymgav-image.png

UserStatusService 고도화

  • create

    • DTO를 활용해 파라미터를 그룹화합니다.
    • 관련된 User가 존재하지 않으면 예외를 발생시킵니다.
    • 같은 User와 관련된 객체가 이미 존재하면 예외를 발생시킵니다.
  • find

    • id로 조회합니다.
  • findAll

    • 모든 객체를 조회합니다.
  • update

    • DTO를 활용해 파라미터를 그룹화합니다.
    • 수정 대상 객체의 id 파라미터, 수정할 값 파라미터
  • updateByUserId

    • userId 로 특정 User의 객체를 업데이트합니다.
  • delete

    • id로 삭제합니다.
    • 의존성
    • 같은 레이어 간 의존성 주입은 순환 참조 방지를 위해 지양합니다. 다른 Service 대신 필요한 Repository 의존성을 주입해보세요.

wms3box4u-image.png

BinaryContentService 구현

  • create

    • DTO를 활용해 파라미터를 그룹화합니다.
  • find

    • id로 조회합니다.
  • findAllByIdIn

    • id 목록으로 조회합니다.
  • delete

    • id로 삭제합니다.
    • 의존성
    • 같은 레이어 간 의존성 주입은 순환 참조 방지를 위해 지양합니다. 다른 Service 대신 필요한 Repository 의존성을 주입해보세요.

1vjxng9jx-image.png

새로운 도메인 Repository 구현체 구현

  • 지금까지 인터페이스로 설계한 각각의 Repository를 JCF, File로 각각 구현하세요.

w9rzlpt8k-image.png

멘토님께

  • 늦게 올려 죄송합니다..!! ㅠ ㅠ

@JinsolPark JinsolPark added 미완성🛠️ 스프린트 미션 제출일이지만 미완성했습니다. 죄송합니다. 순한맛🐑 마음이 많이 여립니다.. 지각제출⏰ 제출일 이후에 늦게 제출한 PR입니다. labels May 6, 2025
@JinsolPark JinsolPark requested a review from ssjf409 May 6, 2025 15:34
UUID userId,
String channalName,
List<UUID> entry,
boolean isPriavate
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오타있습니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 그리고 이미 PrivateChannelCreateDTO이냐 PublicChannelCreateDTO에 따라서 어차피 로직이 다르게 동작할것 같은데 isPrivate 필드가 굳이 필요한지 저는 잘 모르겠어요. 있어도 상관은 없는데, 저는 안 썼을거 같아요. 이미 DTO 이름에서 의미가 명확하게 들어나서요.

public UserStatus(UUID userId) {
this.id = UUID.randomUUID();
this.userId = userId;
this.isLogin = true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 코드 작성 의도가 궁금해서 여쭤보는겁니다.
UserStatus 항상 로그인 된 User가 호출할때만 생성되는 객체인가요?

User 입장에서 이미 로그인 했으면 login이 유지된 상태를 전제로 동작할거 같아요. 그래서 isLogin 필드는 굳이 필요없을거 같긴합니다.


Message save(Message message);
List<Message> findAll();
List<Message> findAllFromChannel(UUID channel);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

findAllByChannel(UUID channelId)는 어떠신가요?

Comment on lines +18 to +21
boolean existsId(UUID id);
boolean existsUsername(String username);
boolean existsEmail(String email);
boolean existsName(String name);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

18~ 21 라인들에 전부 중간에 By 넣어주면 좋을거 같아요.


// 아이디, 이메일 중복체크
public boolean isDuplicated(String args) {
return userRepository.findAll().stream()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

모든 유저를 다 가져와서 찾는건 너무 비효율적인거 같아요.
repository 레벨에서 username이랑 email로 동일한 유저가 있는지 조회하는 코드로 바꿔주실 수 있을까요?
이게 진솔님은 조금 낯서실수 있는데, RDB에서 특정 컬럼만 가지고 조회가 가능해요.

SELECT * FROM users WHERE username = '유저네임'
SELECT * FROM users WHERE email = '[email protected]'

그래서 이렇게 repository 에서 조건 걸어서 조회가 가능하다고 가정하시고 코드 작성해주시면 좋을거 같아요.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +154 to +165
if (userRepository.existsId(id)) {
userRepository.delete(id);

// 관련 도메인 삭제
binaryContentRepository.deleteByUserId(id);
userStatusRepository.deleteByUserId(id);

} else if (!userRepository.existsId(id)) {
throw new NoSuchElementException("해당 사용자가 존재하지 않습니다.");
} else {
throw new IllegalArgumentException("잘못된 접근입니다.");
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 if-else가 자주 나오는 형태가 가독성이 떨어지는 문제가 있습니다.
early return 이라는게 있어요. early return이 항상 좋은건 아닌데, 지금같은 상황에선 좋은거 같아요. 시간 되실때 한번 살펴보시겠어요?

참고 : https://woonys.tistory.com/209

아 그리고 154 번째 라인에서 userRepository.existsId(id)값이 true인지 false인지 판단했잖아요. 그러면 161번째 라인에서 다시 userRepository.existsId(id)를 호출할 필요는 없어보여요.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

early return으로 코드를 작성하면

if (!userRepository.existsId(id)) {
    throw new NoSuchElementException("해당 사용자가 존재하지 않습니다.");
}
userRepository.delete(id);

// 관련 도메인 삭제
binaryContentRepository.deleteByUserId(id);
userStatusRepository.deleteByUserId(id);

Comment on lines +91 to +92
PublicChannelCreateDTO publicChannelCreateDTO = new PublicChannelCreateDTO(user1.getId(), "첫번째 채팅방", false);
PrivateChannelCreateDTO privateChannelCreateDTO = new PrivateChannelCreateDTO(user1.getId(), "두번째 채팅방", null, true);
Copy link
Collaborator

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

private final UUID id;
private String name;
private final UUID maker;
private final boolean isPrivate;
private List<UUID> entry;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

entry라는 List 필드로 UserId 관리하지 말고
ReadStatus로 User랑 Channe의 관계를 표현하는게 더 좋을거 같아요.


}

public boolean checkUser(UUID id) throws IllegalAccessException {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 메서드는 언제 호출되는거에요?

@ssjf409
Copy link
Collaborator

ssjf409 commented May 6, 2025

고생하셨습니다. 진솔님, 코드 완료되면 저한테 코드 변경했다고 답글에 멘션 걸어서 한번만 알려주세요.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
미완성🛠️ 스프린트 미션 제출일이지만 미완성했습니다. 죄송합니다. 순한맛🐑 마음이 많이 여립니다.. 지각제출⏰ 제출일 이후에 늦게 제출한 PR입니다.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants