UserRepositoryV4.java
public interface UserRepositoryV4 {
Long save(User user);
Optional<User> findById(Long userId);
void remove(Long userId);
}
UserRepositoryV4Impl.java
@RequiredArgsConstructor
public class UserRepositoryV4Impl implements UserRepositoryV4{
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);
}
}
UserServiceV4.java
@Transactional
public class UserServiceV4 {
private final UserRepositoryV4 userRepositoryV4;
public UserServiceV4(EntityManager em) {
this.userRepositoryV4 = new UserRepositoryV4Impl(em);
}
public Long join(User user){
return userRepositoryV4.save(user);
}
public User findOne(Long userId){
Optional<User> findUser=userRepositoryV4.findById(userId);
return findUser.orElseThrow(()->{
throw new RuntimeException();
}
);
}
}
UserServiceV4Test.java
@SpringBootTest
@Transactional
public class UserServiceV4Test {
@Autowired EntityManager em;
@Test
@DisplayName("V4 service test")
void v4_서비스_테스트(){
User user=createUser("hong","123");
UserServiceV4 userServiceV4=new UserServiceV4(em);
Long saveId = userServiceV4.join(user);
User findUser = userServiceV4.findOne(saveId);
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();
}
}
동작확인
문제점
userServiceV4
클래스를 보시면 여전히 구체적인 클래스에 종속되고 있다는 것을 보실 수 있습니다!
public UserServiceV4(EntityManager em) {
this.userRepositoryV4 = new UserRepositoryV4Impl(em);
}
흠.. 그렇다면 클래스 이름을 넣어서 오브젝트를 만들지 않으면 어떻게 사용할 수 있을까요..
먼저 Ver 4. 에서의 문제를 보면 userService
와 userService
가 사용할 userRepository
의 특정 구현 클래스 사이의 관계를 설정해주는 것에 대한 관심이 잔존하고 있다는 문제가 있었습니다.
이 관심사를 담은 코드를 분리하지 않으면 독립적으로 확장 가능한 클래스가 될 수 없을 것입니다.
해결 방법
service
와 repository
의 관계를 결정해주는 기능을 분리해 두면 됩니더.자세히
service
오브젝트가 repository
인터페이스와 관계를 맺으려면 인터페이스르 구현한 오브젝트가 있어야 할텐데, 이를 굳이 service
오브젝트에서 해주는위의 특징들을 이용하여 이제는 관심사의 분리가 완벽해 졌으며, 진정으로 변경과 확장이 용이해졌습니다.
Code
UserRepositoryV5.java
public interface UserRepositoryV5 {
Long save(User user);
Optional<User> findById(Long userId);
void remove(Long userId);
}
UserRepositoryV5Impl.java
@RequiredArgsConstructor
public class UserRepositoryV5Impl implements UserRepositoryV5 {
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);
}
}
UserServiceV5.java
@Transactional
public class UserServiceV5 {
private final UserRepositoryV5 userRepositoryV5;
public UserServiceV5(UserRepositoryV5 userRepositoryV5) {
this.userRepositoryV5 = userRepositoryV5;
}
public Long join(User user){
return userRepositoryV5.save(user);
}
public User findOne(Long userId){
Optional<User> findUser=userRepositoryV5.findById(userId);
return findUser.orElseThrow(()->{
throw new RuntimeException();
}
);
}
}
AppConfig.java
@RequiredArgsConstructor
public class AppConfig {
private final EntityManager em;
public UserRepositoryV5 userRepository(){
return new UserRepositoryV5Impl(em);
}
public UserServiceV5 userService(){
return new UserServiceV5(userRepository());
}
}
AppConfig
를 클라이언트 오브젝트라고 생각하시면 됩니다.
service
클래스의 생성자를 보시면
public UserServiceV5(UserRepositoryV5 userRepositoryV5) {
this.userRepositoryV5 = userRepositoryV5;
}
구체적인 오브젝트에 의존하지 않는 것을 보실 수 있습니다.
Test
@SpringBootTest
@Transactional
public class V5Test {
@PersistenceContext EntityManager em;
@Test
@DisplayName("v5 repositoryTest")
void v5_repository_테스트(){
User user=createUser("hong","1234");
UserRepositoryV5 userRepository=new UserRepositoryV5Impl(em);
Long saveId= userRepository.save(user);
User findUser=userRepository.findById(saveId).get();
Assertions.assertThat(findUser.getName()).isEqualTo(user.getName());
Assertions.assertThat(findUser.getPassword()).isEqualTo(user.getPassword());
}
@Test
@DisplayName("v5 total test")
void v5_통합_테스트(){
User user=createUser("hong","123");
AppConfig appConfig=new AppConfig(em);
UserServiceV5 userService= appConfig.userService();
Long saveId = userService.join(user);
User findUser = userService.findOne(saveId);
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();
}
}
동작 확인
Ver 1. 에서 Ver 5. 에 걸쳐 서로 영향을 주지 않으면서도 필요에 따라 자유롭게 확장할 수 있는 구조를 완성 했습니다!!!