Feedback1)ChannelService의 create() 받는 부분을 User owner에서 UUID ownerId로 변경하여라.
이유.
- id로 받는 이유는 객체로 받을경우 그 객체가 내가 찾는 객체인지 보장할 수 없다.
- 주소값이 같은걸로 판단할것인지, User의 name만 같으면 같은 객체인지 판단할 수 없다.
- 또 FE에서는 js를 쓰기때문에 객체로 넘길 수 없다. 그래서 식별자를 통해 전달하고 해당 id를 가지고 객체를 직접조회해야 함.
JCFChannenlService(구현체)
@Override
public Channel create(String channelName, String type, User owner){
Channel channel = new Channel(channelName,type,owner);
data.put(channel.getId(),channel); //data에 key value 값으로 넣음.
return channel;
}
create()의 매개변수로 User owner를 UUID ownerId로 받게하라는 피드백이 있다.
User owner을 UUID ownerId로 변경하면 생성자에 영향이 있다.
create()문은 말그대로 채널 객체를 생성하는것.
Channel 클래스
public class Channel extends Common{
private String channelName;
private String type;
private User owner;
private List<User> userList;
private List<Message> messageList;
public Channel(String channelName, String type, User owner){
//super();
this.channelName = channelName;
this.type = type;
this.owner = owner; //채널의 소유주
this.userList = new ArrayList<>();
this.userList.add(owner); // 소유자는 기본 참여자;
}
Channel의 User owner 자체를 UUID ownerId로 바꾸고 매개변수도 UUID ownerId로 한다.
그럼 생성자내 User 객체가 없고,
생성도 못하므로 채널이 생성될때 해당 채널에 소유주를 추가하는 userList.add는 create문 쪽으로 빠져야될거같다.
- User owner 필드를 UUID ownerId로 변경
- 생성자내 userList.add를 JCFChannelService의 create()문 내로 따로 빼기
[수정후] JCFChannelService
@Override
public Channel create(String channelName, String type, UUID ownerId){
Channel channel = new Channel(channelName,type,ownerId);
User user = userService.findById(ownerId);
channel.setUserList(user);//방장을 Channel의 List<User>에 add
data.put(channel.getId(),channel); //data에 key value 값으로 넣음.
return channel;
}
- UUID ownerId로 받게 수정완료
- Channel내 List<User> userList에 채널 생성할때 방장(user)를 넣기로함.
- userSevice.findById(ownerId)를 통해 user객체를 만들고 Setter를 통해 필드에 접근해서 add를 하도록 수정.
[재수정]
- Channel의 User owner필드는 그대로 둔다. -> 생성자도 User owner로 그대로 둔다.
- JCFChannelService 측에서 객체 만들고 생성자에는 객체 전달하는식으로 코드 수정.
[재수정한 JCFChannelService]
@Override
public Channel create(String channelName, String type, UUID ownerId){
User owner = userService.findById(ownerId);
Channel channel = new Channel(channelName,type,owner);
channel.getUserList.add(owner) //방장 채널에 넣기
data.put(channel.getId(),channel); //data에 key value 값으로 넣음.
return channel;
}
- create()는 id로 받게 수정.
- id를 통해 User 객체 만들어서 생성자로 전달.
- userList.add(owner)는 Service 단으로 뺐다.
Feedback2)반환 타입을 User로 하면 유저쪽으로 이관해야되는지 고민을 해야된다.
JCFChannelService
@Override
public List<User> enter(UUID channelId,UUID userId){ //채널 id가 들어온다.
Channel channel = findById(channelId);
User user = userService.findById(userId);
channel.enter(user);
user.addChannel(channel);
return channel.getUserList();
}
@Override
public List<User> leave(UUID channelId,UUID userId){
Channel channel = findById(channelId);
User user = userService.findById(userId);
channel.leave(user);
user.removeChannel(channel);
return channel.getUserList();
}
- 현재 enter와 leave는 채널 입장,퇴장 메서드이다.
- 입장을 하고, 참석돼있는 userList를 보여준다.
- 단순히 입장및 퇴장의 기능만 하도록 하고 조회는 다른 메서드를 통해 하도록한다.
- List<User>로 돼있는 타입을 void로만 수정
Feedback3)
참여하고있는 채널에 입장하면 예외/참여안하고 있는 채널에서 나가려하면 예외 코드를 도메인객체가 아닌 Service로 이관
이유.
- 도메인 클래스는 필드와 필드의 getter,setter만 유지하는게 좋다.
- 정책이 변경되면(입퇴실에 대한) 도메인 객체까지 수정사항이 전파될 수 있다.
Channel(도메인 객체)
public void enter(User user){
if(userList.contains(user)){
throw new IllegalArgumentException("이미 참여하고 있는 사용자입니다.");
}
userList.add(user);
}
public void leave(User user){
if(user.equals(owner)){
throw new IllegalArgumentException("방장은 퇴장할 수 없습니다.");
}
if(!(userList.contains(user))){
throw new IllegalArgumentException(user.getUserName() + "가 채널에 없습니다.");
}
userList.remove(user);
}
이걸 Service 단으로 옮기자.
수정후 JCFChannelService
@Override
public void enter(UUID channelId,UUID userId){ //채널 id가 들어온다.
Channel channel = findById(channelId);
User user = userService.findById(userId);
if(channel.getUserList().contains(user)){
throw new IllegalArgumentException("이미 참여하고 있는 유저입니다.");
}
channel.getUserList().add(user);
user.addChannel(channel);
}
@Override
public void leave(UUID channelId,UUID userId){
Channel channel = findById(channelId);
User user = userService.findById(userId);
if(user.equals(channel.getOwner())){
throw new IllegalArgumentException("방장은 퇴장할 수 없습니다.");
}
channel.getUserList().remove(user);
user.removeChannel(channel);
}
Channel의 enter,leave를 Service로 이관하였다.
빠른 정책변경이 가능하다.
Feedback4)
채널을 찾는 기능은 채널 서비스로, 메시지를 찾는 기능은 메시지 서비스로 이관하자.
이유: UserService에 둘경우 확장성이 낮음./ 각각에 두면 도메인 책임이 강해짐. 확장성 좋음.
JCFUserService
public List<Channel> selectChannel(UUID id){
User user = findById(id);
return user.getChannelList();
}
public List<Message> selectMessage(UUID id){
User user = findById(id);
return user.getMessageList();
}
User 기준에서 User의 채널과 메시지를 select 하는거라 UserService에 넣었는데
각각 ChannelService와 MessageService로 나눠야함.
JCFMessageService
public List<Message> findByUser(UUID userId){
User user = userService.findById(userId);
return user.getMessageList();
}
UserService에 있던걸 MessageService로 옮겼다.
userId를 받고 객체 만들어서 객체 접근해서 messageList 가져오게 하였다.
JCFChannelService
public List<Channel> findByUser(UUID userId){ //특정유저가 참여하고 있는 채널
User user = userService.findById(userId);
return user.getChannelList();
}
ChannelService도 마찬가지.
Feedback5)
update()메서드를 유저이름만 수정할수도, 이메일만수정할수도등 원하는 데이터만 수정할 수 있게 기능을 유연하게 만들어봐라.
[현재 JCFUserServie의 코드]
@Override
public User update(UUID id,String userName,String email,String status){
User user = findById(id);
user.setUserName(userName);
user.setEmail(email);
user.setStatus(status);
return user;
}
- update를 하려면 수정하고싶지않은 다른 매개변수도 보내줘야한다.
- Optional.ofNullable()을 사용해서 바꾸고싶은 값만 바뀌도록 수정해보자.
Optionanl이란?
- Optional<T>는: T타입 값이 있을수도, 없을수도 있다를 표현하는 객체
- null 직접 사용을 줄인다.
- NPE방지
- 없을 수 있음을 코드로 드러낸다.
Optional.ofNullable(value)
null이면 비어있는 Optionanl, 값이 있으면 그 값을 감싼 Optional
예제)Map에서 값 조회
User user = data.get(id);
id가 없으면 null
Optional<User> userOpt = Optional.ofNullable(data.get(id));
이걸 Optional로 감싸면 null일때 아닐때를 NPE 방지할 수 있다.
data.get(id)값이 null이면 userOpt = Optional.empty()라는 뜻이다.
User user = Optional.ofNullable(data.get(id))
.orElseThrow(() ->
new IllegalArgumentException("유저 없음")
);
이런식으로 null일때 예방가능.
update를 어떻게 바꿔야되지?
- id는 꼭 필요하고, userName이나 email,status가 null이라면 getName하고 null이 아니라면 setName(name)
내가 생각한 코드
@Override
public User update(UUID userId,String userName,String email,String status){
User user = findById(userId);
Optional.ofNullable(user).map(User :: setUserName(userName)).orElse(User ::getUserName());
userId로 user객체 만들고,
Optional.ofNullable로 user 넣어서 값이 있다면 setUserName을 하고,
값이 없다면 getUserName을 하는건데
user객체를 만들었을때 해당 객체가 userId에 따라 있거나 없는걸로 이미 결정이 남.
따라서 위 코드같이 하면 안됨.
[수정후 코드]
@Override
public User update(UUID userId,String userName,String email,String status){
if (userId == null) {//Optional은 값이 없을수도 있다의 의미라서 userId는 Optional을 쓰지않는다.
throw new IllegalArgumentException("수정할 유저ID를 입력해주세요");
}
User user = findById(userId);
Optional.ofNullable(userName).ifPresent(user :: setUserName);//userName값이 있으면 setter
Optional.ofNullable(email).ifPresent(user :: setEmail);
Optional.ofNullable(status).ifPresent(user :: setStatus);
return user;
}
- 이렇게 하면 똑같이 main에서 update를 하려면 모든 매개변수를 입력해줘야한다.
- 수정 안할 값에도 null을 억지로 채워야한다.
userService.update(userId, null, "test@gmail.com", null);
이런식으로 ...
이것이 나중에 DTO로 받을때 쉽게 대응할 수 있다.
'코드잇 스프린트 > 실습' 카테고리의 다른 글
| [sprint5] 디스코드 어플리케이션 채널 생성시 NPE 발생 (0) | 2026.02.23 |
|---|---|
| BasicUserService내 update 기능 피드백. (0) | 2026.01.29 |
| ReadStatus,UserStatus,BinaryContent 도메인 추가(왜 추가하는거고, 어떤 역할을 하는지?) (0) | 2026.01.28 |
| 피드백) 단건조회는 null 반환 가능성이 있어 Optional로 래핑해라 (0) | 2026.01.21 |
| 간단한 디스코드 프로그램 만들기_ 각 필드에 List 추가해서 기능 추가하기. (0) | 2026.01.14 |
