Skip to content

[강우진] sprint3 #82

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 4 commits into
base: 강우진
Choose a base branch
from

Conversation

WJKANGsw
Copy link
Collaborator

@WJKANGsw WJKANGsw commented May 4, 2025

시간 타입 변경하기
[ ] 시간을 다루는 필드의 타입은 Instant로 통일합니다.
기존에 사용하던 Long보다 가독성이 뛰어나며, 시간대(Time Zone) 변환과 정밀한 시간 연산이 가능해 확장성이 높습니다.
새로운 도메인 추가하기
도메인 모델 간 참조 관계를 참고하세요.

[ ] 공통: 앞서 정의한 도메인 모델과 동일하게 공통 필드(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 의존성을 주입해보세요.

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 설정 값을 통해 제어해보세요.

application.yaml

discodeit:
repository:
type: jcf # jcf | file
[ ] discodeit.repository.type 설정값에 따라 Repository 구현체가 정해집니다.
[ ] 값이 jcf 이거나 없으면 JCFRepository 구현체가 Bean으로 등록되어야 합니다.
[ ] 값이 file 이면 File
Repository 구현체가 Bean으로 등록되어야 합니다.
[ ] File*Repository 구현체의 파일을 저장할 디렉토리 경로를 application.yaml 설정 값을 통해 제어해보세요.

스크린샷

image
image
image
image

sprint4 미션하기전에 sprint3 제출합니다.

@WJKANGsw WJKANGsw added 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. 미완성🛠️ 스프린트 미션 제출일이지만 미완성했습니다. 죄송합니다. labels May 4, 2025
@WJKANGsw WJKANGsw requested a review from IDontHaveBrain May 4, 2025 14:54
@WJKANGsw WJKANGsw changed the title 강우진 sprint3 [강우진] sprint3 May 4, 2025
Copy link
Collaborator

@IDontHaveBrain IDontHaveBrain left a comment

Choose a reason for hiding this comment

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

대부분 코드가 깔끔하시고 정석에 가까운거 같습니다!

@Repository
public class JcfReadStatusRepository implements ReadStatusRepository {

private final Map<UUID, ReadStatus> storage = new HashMap<>();
Copy link
Collaborator

Choose a reason for hiding this comment

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

HashMap은 스레드에 안전하지 않습니다.
Spring Service Bean은 기본적으로 싱글톤이므로 여러 스레드에서 동시에 접근할 수 있습니다. (여러 사용자가 동시에 cru 작업을 요청하면 하나의 JcfMessageRepository 오브젝트에 있는 xxxMap을 동시에 수정을 시도하게 됩니다.)

이를 해결하는 방안도 다양한 방법이 있을거 같습니다.
ConcurrentHashMap, synchronized, 로직적 Lock체크 이것 외에도 다른 방법들이 더 있을수 있습니다.
각 방식을 한번 서칭하시고 개념을 알아두시면 좋을거 같습니다!

다른 JCF Repository 구현체들도 동일하게 적용됩니다.

public class BasicMessageService implements MessageService {
private final MessageRepository messageRepository;
private final UserService userService;
private final ChannelService channelService;
private final ChannelRepository channelRepository;
private final BinaryContentRepository binaryContentRepository;

private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MM월 dd일 a hh시 mm분 ss초");
Copy link
Collaborator

Choose a reason for hiding this comment

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

private static final 로 선언하여 재사용 하신점은 아주 좋습니다!

단 SimpleDateFormat은 스레드에 안전하지 않습니다.
여러 스레드가 동시에 DATE_FORMAT.format()을 호출하면 예기치 않은 결과나 오류가 발생할 수 있습니다.

SimpleDateFormat, Date 클래스 모두 Java 8 이전부터 존재하던 조금 오래된 클래스입니다.
실무에서 코드베이스가 이미 날짜와 시간에 Date를 사용하는 상황이 아니라면 java.time.* 하위 패키지를 사용하시길 권장드립니다.
좀더 자세한 내용은 https://d2.naver.com/helloworld/645609 이 블로그글 한번 참고하시면 좋을거 같습니다!

.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 채널입니다."));

String newChannelName = request.channelName();

if (channel.getChannelName().equals(newChannelName)) {
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.

요구사항에 "PRIVATE 채널은 수정할 수 없습니다"라는 내용이 있습니다.
현재 update 메소드는 채널 타입에 대한 검사 없이 PUBLIC 채널과 동일하게 처리하고 있습니다.

throw new IllegalArgumentException("userStatus가 존재하지 않음");
}

if (userStatusRepository.find(userId).isPresent()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

여기서는 userId로 UserStatus를 찾는 것이 아니라 UserStatus의 ID로 찾아야 할 것 같습니다.
User ID로 UserStatus를 찾는 방향이라면 findByUserId(userId) 같은 메소드가 Repository에 필요합니다.

.filter(user -> user.getUsername().equals(request.username()))
.filter(user -> user.getPassword().equals(request.password())) // 단순 비교
.findFirst() // 조건 만족하는 첫 번째 유저
.map(user -> new UserDto(user, false, false)) // UserDto로 변환하여 비밀번호 정보 제외
Copy link
Collaborator

Choose a reason for hiding this comment

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

BasicUserService.find() 에서처럼 Repository 조회하여 hasProfileImage, isOnline 설정하시면 좋을거 같습니다!

Comment on lines +39 to +52
protected void saveData() {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath))) {
oos.writeObject(dataMap);
} catch (IOException e) {
e.printStackTrace();
}
}

public T save(T entity, ID id) {
dataMap.put(id, entity);
saveData();
return entity;
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

이 케이스에서도 여러명에 사용자가 데이터를 변경하여 save 관련 메서드가 동시에 여러번 호출되는 상황시 에러나 파일이 손상될 수 있습니다.
synchronized, lock 등을 활용하여 방지 가능하니 관련 키워드 한번 검색하여 보시길 추천드립니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. 미완성🛠️ 스프린트 미션 제출일이지만 미완성했습니다. 죄송합니다.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants