좋아요 로직에 대해 생각하다가 생성과 삭제 요청을 분리하지 않고 서버 안에서 처리하게 구현하고자 했다.
그래서 좋아요 관련 로직을 어떻게 처리해야할까 고민하던 중..
원래 처리하던 방식은 두 가지 후보가 있었는데,
첫 번째 방식은 아래처럼 if 문으로 분기처리하여, 존재하지 않는다면 생성하고, 아닐 경우 .get()
을 통해서 값을 가져오는 방식이다.
Optional<Heart> existingHeart = heartRepository.findByMemberIdAndProductId(memberId, productId);
if(existingHeart.isEmpty()) {
Heart newHeart = createHeart(memberId, productId);
return HeartPutResponse.of(newHeart);
}
deleteHeart(existingHeart.get());
return HeartPutResponse.of(null);
첫 번째 방법에서 권장되지 않는 방식인 get
을 사용하는 것이 마음에 들지 않았다.
그렇게 생각해낸 두 번째는 return 문에서 map
함수와 orElseGet
을 통해서 처리하는 방식이다.
Optional<Heart> existingHeart = heartRepository.findByMemberIdAndProductId(memberId, productId);
return existingHeart
.map(heart -> {
deleteHeart(heart);
return HeartPutResponse.of(null);
})
.orElseGet(() -> {
Heart newHeart = createHeart(memberId, productId);
return HeartPutResponse.of(newHeart);
});
이 코드는 map
과 orElseGet
을 사용하여 분기문을 처리하고 있지는 않지만, 메서드 체이닝이 길어 가독성이 떨어진다는 단점이 있다.
함수형 인터페이스와 Optional 종단 처리 메서드에 대해 정리해보고 위의 코드를 개선해보고자 한다.
Optional
우선 Optional은 JDK 8 부터 고안된 null을 대신하기 위해 만들어진 데이터 타입 클래스이다. null이나 null이 아닌 값을 담을 수 있다.
보통 우리는 JPA에서 개발할 때 findBy
함수의 리턴 값으로 Optional 객체를 반환받는다. 그렇게 하므로써 NPE가 발생할 가능성을 줄일 수 있기 때문이다.
그러면 Optional이 null 일 경우, 조건부 처리를 하는 방법이 무엇일까?
ifPresent
public void ifPresent(Consumer<? super T> action) {
if (value != null) {
action.accept(value);
}
Consumer
인터페이스 매개변수로 전달받아 값이 존재하는 경우 인수로 넘겨준 Consumer
를 실행한다. 값이 없는 경우 인수로 넘겨준 Consumer
를 실행한다. ifPresentOrElse
ifPresent
메서드와 다르게 매개변수를 하나 더 받는다.public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
if (value != null) {
action.accept(value);
} else {
emptyAction.run();
}
}
첫 번째 매개변수는 Optional
객체에 값이 있을 때 실행되는 Consumer
를 받고 Optional 객체가 비었을 때 실행되는 Runnable
을 받는다.
OrElse
orElse
메서드를 사용하면 비어있는 Optional
객체에 기본값을 설정할 수 있다.public T orElse(T other) {
return value != null ? value : other;
}
Optional
객체에 값이 래핑되어 있다면 그 값을 반환하고, 그렇지 않은 경우에는 설정된 기본 값을 반환한다.
OrElseGet
orElse
와 유사하지만, orElseGet
은 인자로 Supplier
를 받는다.public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}
사용 방법
Heart heart = heartRepository.findByProductAndUser(product, user).OrElseGet(() -> Heart.create(product, user));
orElse
메서드와orElseGet
메서드의 차이
orElseGet
메서드의 경우 Optional
에 값이 없을 때만 실행되지만 orElse
메서드는 값의 존재 여부와 관계 없이 항상 실행된다.
OrElseThrow
Optional
객체에 래핑된 값이 없을 경우 예외를 발생시킨다.public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
Optional 메서드 인자로 주어지는 함수형 인터페이스에 대해 톺아보자.
Java에서 제공하는 함수형 인터페이스는 Supplier<T>
,Consumer<T>
Function<T,R>
Predicate<T>
이 있다.
Optional에서 인자로 전달되는 두 인터페이스인 Supplier<T>
,Consumer<T>
만 알아보자.
Consumer<T>
void accept(T t)
를 추상메소드로 갖는다.@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
이 인터페이스가 사용되는 ifPresent
메서드를 통해 사용 예시를 살펴보자면,
existingHeart.ifPresent(this::deleteHeart);
deleteHeart 함수는 리턴 값이 없기 때문에 위와 같이 사용할 수 있다.
실제로 이 매개변수에 리턴 값을 넣어주면 에러가 발생한다.
Supplier<T>
@FunctionallInterface
public interface Supplier<T>{
T get();
}
이 인터페이스를 사용하는 orElseGet
을 통해 사용 예시를 살펴보자면,
existingHeart.map(heart -> {
deleteHeart(heart);
return HeartPutResponse.of(null);
}).orElseGet(() -> {
Heart newHeart = createHeart(memberId, productId);
return HeartPutResponse.of(newHeart);
})
이렇게 리턴 값을 지정해줄 수 있다.
get을 사용하지 않고 존재할 경우, 삭제하고 비어있을 경우 생성하는 로직을 작성했다.
Optional<Heart> existingHeart = heartRepository.findByMemberIdAndProductId(memberId, productId);
if (existingHeart.isEmpty()) {
Heart newHeart = createHeart(memberId, productId);
return HeartPutResponse.0f(newHeart);
} else {
existingHeart.ifPresent(this::deleteHeart);
return HeartPutResponse.of(null);
}
Optional
객체값을 두 번 참조하는 단점이 있었기 때문에 코드를 조금 더 개선해 볼 방법에 대해 고민해보았다.
if (existingHeart.isEmpty())
와 existingHeart.ifPresent(this::deleteHeart);
에서
두번 분기 처리되는 방식을 개선해볼 방향을 고민하던 중 기존에 작성했던 .map
을 사용한 방식을 채택하기로 했다 😅
개선하진 않았지만 이번 기회에 Optional
과 함수형 인터페이스에 대해 살펴보았다!