Repository 의존성 역전!
public class EntityFinder {
public static <T> T findByIdOrThrow(JpaRepository<T, Long> repository, Long id, ErrorCode errorCode) {
return repository.findById(id)
.orElseThrow(() -> new InvalidRequestException(errorCode));
}
}
public class EntityValidator {
public static <T> void isExistsById(JpaRepository<T, Long> repository, Long id, ErrorCode errorCode) {
if(id == null){
throw new InvalidRequestException(ErrorCode.ID_NULL);
}
if(!repository.existsById(id)) {
throw new InvalidRequestException(errorCode);
}
}
}
현재 entity를 가져오거나 검증하는 로직을 이런식으로 static 메서드로 정의해 사용중이었다.
일단 중복을 없앰에 있어서는 분명히 좋은 구조 같지만 왠지 모를 불편함을 느꼈다.. 일단 파라미터로 repository를 사용하는 것도 별로고..
인터페이스를 만들어서 각각의 repository가 상속받아 사용하게 하면 훨씬 편할 것 같았다. JPA repository중 하나를 내가 만든다는 느낌으로 접근하면 좋을거다.
public interface CustomRepository<T> {
T findByIdOrThrow(Long id, ErrorCode errorCode);
void validateExistsById(Long id, ErrorCode errorCode);
}
findById와 validate를 수행하는 CustomRepository를 만들었다.
@RequiredArgsConstructor
public class CustomRepositoryImpl<T> implements CustomRepository<T> {
private final JpaRepository<T, Long> repository;
@Override
public T findByIdOrThrow(Long id, ErrorCode errorCode) {
return repository.findById(id)
.orElseThrow(() -> new InvalidRequestException(errorCode));
}
@Override
public void validateExistsById(Long id, ErrorCode errorCode) {
if (id == null) {
throw new InvalidRequestException(ErrorCode.ID_NULL);
}
if (!repository.existsById(id)) {
throw new InvalidRequestException(errorCode);
}
}
}
구현체도 만들어주고,
public interface UserRepository extends JpaRepository<User, Long>, CustomRepository<User> {
Optional<User> findByEmail(String email);
boolean existsByEmail(String email);
}
User user = userRepository.findByIdOrThrow(userId, ErrorCode.USER_NOT_FOUND);
이제 서비스 계층에서 jpa에서 제공하는 repository 메서드 인것마냥 사용할 수 있다 ! 편안 ~ 허다
같은 service 계층에서의 구체적인 구현체가 아니라, repository에 의존하도록 DIP를 준수했다고 볼 수 있다.
++하지만 이 코드가 잘 동작이 안돼서.. 이렇게 repository관련 코드가 겹칠 땐 어떻게 해결해야 하는지 좀 찾아봐야겠다 ㅠ
마냥 서비스코드 늘려서 제네릭만 쓰긴 싫다..
entityManager로도 해보고, 임플클래스를 늘려도 봤는데 의존성쪽에서 계속 문제가 생긴다..흑흑 튜터님 도움을
Mapper 의존성 역전!
public interface Mapper<E, S> {
S toDto(E entity);
}
private final TodoMapper todoMapper;
@PostMapping("/todos")
public ResponseEntity<TodoResponse> saveTodo(
@Auth AuthUser authUser,
@Valid @RequestBody final TodoSaveRequestDto dto
) {
Todo todo = todoService.saveTodo(authUser, dto);
return ResponseEntity.ok(todoMapper.toDto(todo));
}
mapper 인터페이스를 만들어놓고 멍청하게 구현체를 주입받아 사용하고 있었다. 바보가 틀림없어 나는
그저 인터페이스로 분리만 해버린 사람..
private final Mapper<Todo, TodoResponse> mapper;
@Transactional
public TodoResponse saveTodo(AuthUser authUser, final TodoSaveRequestDto dto) {
User user = User.fromAuthUser(authUser);
String weather = weatherClient.getTodayWeather();
Todo newTodo = Todo.of(dto.title(), dto.contents(), weather, user);
todoRepository.save(newTodo);
return mapper.toDto(newTodo);
}
이왕 이렇게 된거 mapper로직 서비스로 옮기고 추상화된 개념에 의존하도록 했다!
예전에는 제네릭을 도대체 어따 써??? 했는데 역시 아는 만큼 보인다고.. 점점 제네릭이 사랑스러워진다.
추상화된 상위 클래스에 의존하도록 해서, DIP를 만족했다고 볼 수 있다!
전의 tokenProvider처럼 패키지 의존성의 흐름 방향을 바꾸었다곤 못 하겠지만, 역시 상위 개념에 의존하도록 하는 것이 유지보수가 편리해진다~
'개인 공부용 > sparta-expert' 카테고리의 다른 글
Lv.6 리팩토링을 리팩토링하기 (0) | 2025.01.02 |
---|---|
Lv.4 N+1 문제 개선하기 (0) | 2025.01.01 |
Lv.5 테스트코드 수정하기 (0) | 2025.01.01 |