Version 1. 에서의 코드를 보시면 한 가지 클래스내에 데이터베이스에 접근하는 메소드와 데이터베이스를 이용하여 비즈니스 로직을 담당하고 있는 메소드가 있습니다.
이 둘의 관심사를 repository
와 service
로 분리시켜 봅시다.
어떻게 할까요??!
UserRepositoryV2.java
public abstract class UserServiceV2 {
public Long join(User user){
return save(user);
}
public User findOne(Long userId){
Optional<User> findUser=findById(userId);
return findUser.orElseThrow(()->{
throw new RuntimeException();
}
);
}
public abstract Long save(User user);
public abstract Optional<User> findById(Long userId);
public abstract void remove(Long userId);
}
UserRepositoryV2Extends.java
@RequiredArgsConstructor
public class UserRepositoryV2Extends extends UserServiceV2{
private final EntityManager em;
@Override
public Long save(User user) {
em.persist(user);
return user.getId();
}
@Override
public Optional<User> findById(Long userId) {
return Optional.ofNullable(em.find(User.class,userId));
}
@Override
public void remove(Long userId) {
User findUser = findById(userId).orElse(null);
if(findUser==null)
throw new IllegalArgumentException();
em.remove(findUser);
}
}
UserServiceV2Test.java
@SpringBootTest
@Transactional
public class UserServiceV2Test {
@Autowired
EntityManager em;
@Test
@DisplayName("V2 test")
void V2_테스트(){
User user=createUser("hong","1234");
UserServiceV2 userService=new UserRepositoryV2Extends(em);
Long saveId = userService.join(user);
User findUser = userService.findOne(saveId).get();
Assertions.assertThat(findUser.getName()).isEqualTo(user.getName());
Assertions.assertThat(findUser.getPassword()).isEqualTo(user.getPassword());
}
private User createUser(String name, String password){
return User.createUser()
.name(name)
.password(password)
.build();
}
}
테스트 결과
추상 클래스를 활용하여 repository
의 변경 작업을 한층 더 용이하게 사용할 수 있게 되었습니다.
repository
클래스의 코드를 변경할 필요없이 기능을 새롭게 정의한 클래스를 만들 수 있습니다.👍 템플릿 메소드 패턴
기본적인 로직의 흐름을 만들고, 그 기능의 일부를 추상 메소드나 오버라이딩이 가능한 메소드 등으로 만든 뒤
서브클래스에서 이런 메소드를 필요에 맞게 구현해서 사용하도록 하는 방법을 디자인 패턴에서 템플릿 메소드 패턴이라고 합니다.
BUT, 변경 및 확장이 용이해졌지만 상속을 활용했다는 단점이 존재
repository
관련된 부분을 상속클래스가 아닌 아예 별도의 클래스로 만들어봅시다.UserRepositoryV3.java
@RequiredArgsConstructor
public class UserRepositoryV3 {
private final EntityManager em;
public Long save(User user){
em.persist(user);
return user.getId();
}
public void remove(Long id){
User findUser = findById(id).orElse(null);
if(findUser==null)
throw new IllegalArgumentException();
em.remove(findUser);
}
public Optional<User> findById(Long id){
return Optional.ofNullable(em.find(User.class,id));
}
public Optional<User> findByName(String name){
return Optional.ofNullable(em.createQuery("select u from User u where u.name=:name",User.class)
.setParameter("name",name)
.getSingleResult());
}
}
UserServiceV3.java
@Transactional
public class UserServiceV3 {
private final EntityManager em;
private final UserRepositoryV3 userRepositoryV3;
public UserServiceV3(EntityManager em) {
this.em = em;
this.userRepositoryV3 = new UserRepositoryV3(em);
}
public Long join(User user){
return userRepositoryV3.save(user);
}
public User findOne(Long userId){
Optional<User> findUser=userRepositoryV3.findById(userId);
return findUser.orElseThrow(()->{
throw new RuntimeException();
}
);
}
}
UserServiceV3Test.java
@SpringBootTest
@Transactional
public class UserServiceV3Test {
@Autowired EntityManager em;
@Test
@DisplayName("v3 service test")
void service_v3_테스트(){
User user=createUser("hong","123");
UserServiceV3 userServiceV3=new UserServiceV3(em);
Long joinId = userServiceV3.join(user);
User findUser = userServiceV3.findOne(joinId);
Assertions.assertThat(findUser.getName()).isEqualTo(user.getName());
Assertions.assertThat(findUser.getId()).isEqualTo(joinId);
}
private User createUser(String name, String password){
return User.createUser()
.name(name)
.password(password)
.build();
}
}
동작 확인
동작이 되는 것을 확인하실 수 있습니다.
하지만, 한 눈에 보더라도 많은 문제가 존재하죠!
repository
의 확장이 다시 불가능해졌습니다.service
의 코드가 특정 repository
에 종속되었기 때문입니다. repsitory
의 특정 메소드 이름이 바뀌고, 이 메소드를 사용하는 곳이 수 백개 이상이라면 일일히 다 찾아가서 수정하는 작업은 너무 번거롭게 됩니다.repsotory
가 어떤 클래스인지 service
에서 구체적으로 알고 있어야 합니다. 즉, 이 또한 클래스의 변경이 일어난다면, 사용하고 있는 클래스에서 일일히 수정을 해야합니다.결론적으로, 이런 식으로 설계가 된다면, 어떤 클래스가 쓰일지 어떤 메소드가 쓰일지에 대한 정보를 일일히 다 알고 있어야 합니다.
service
는 구체적인 repository
클래스에 종속될수 밖에 없습니다.상속의 문제를 해결하고자 한 것이 더 많은 문제를 초래하게 되었습니다.