SPRING - 오브젝트와 의존관계 (3)

Sungjin·2021년 10월 14일
0

Spring

목록 보기
17/23
post-thumbnail

🔧 Version 4.

  • 인터페이스의 도입
    • 두 개의 클래스가 서로 긴밀하게 연결되어 있지 않도록
    • 자바가 추상화를 위해 제공하는 가장 유용한 도구
    • 오브젝트를 만드려면 구체적인 클래스 하나를 선택해야겠지만 접근하는 쪽에서는 오브젝트를 만들 때 사용할 클래스가 무엇인지 몰라도 됨.
    • 인터페이스는 어떤 일을 하겠다는 기능만 정의해놓은 것입니다.

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);
    }

흠.. 그렇다면 클래스 이름을 넣어서 오브젝트를 만들지 않으면 어떻게 사용할 수 있을까요..

🔧 Version 5.

먼저 Ver 4. 에서의 문제를 보면 userServiceuserService가 사용할 userRepository
의 특정 구현 클래스 사이의 관계를 설정해주는 것에 대한 관심이 잔존하고 있다는 문제가 있었습니다.

이 관심사를 담은 코드를 분리하지 않으면 독립적으로 확장 가능한 클래스가 될 수 없을 것입니다.

해결 방법

  • 클라이언트 오브젝트의 활용
    • 두 개의 오브젝트가 있고, 한 오브젝트가 다른 오브젝트를 사용한다면 사용하는 쪽을 클라이언트 오브젝트, 사용되는 쪽을 오브젝트 서비스라고 합니다.
    • 👍 이를 활용하여 클라이언트 오브젝트에서 servicerepository의 관계를 결정해주는 기능을 분리해 두면 됩니더.

자세히

  • 클래스 사이에 관계가 만들어진다는 것은 한 클래스가 인터페이스 없이 다른 클래스를 직접 사용한다는 뜻.
  • 따라서 클래스가 아니라 오브젝트와 오브젝트 사이의 관계를 설정해줘야 합니다.
    • 오브젝트 사이의 관계는 런타임 시에 한쪽이 다른 오브젝트의 레퍼런스를 갖고 있는 방식으로 만들어 집니다.
  • 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. 에 걸쳐 서로 영향을 주지 않으면서도 필요에 따라 자유롭게 확장할 수 있는 구조를 완성 했습니다!!!

👋 다음 포스팅에서 이어서 하도록 하겠습니다!

profile
WEB STUDY & etc.. HELLO!

0개의 댓글