섹션 3. 역할의 분리와 스프링 컨테이너

섹션 3. 역할의 분리와 스프링 컨테이너

현재 Controller의 문제점

  1. 현재는 API 진입 지점으로써 HTTP Body를 객체로 변환하고 있다.

  2. 현재 유저가 있는지 없는지 확인하고 예외 처리를 해준다.

  3. SQL을 사용해 실제 DB와의 통신을 담당한다.

이를 삼단 분리해보자

1번은 Controller
2번은 Service
3번은 Repository로 분리해보자.

// UserService

public class UserService {
    // 현재 유저가 있는지 없는지 확인하고 예외처리를 해주는 부분

    public void updateUser(JdbcTemplate jdbcTemplate, UserUpdateRequest request) {
        String readSql = "SELECT * FROM user Where id = ?";
        boolean isUserNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, request.getId()).isEmpty();
        if (isUserNotExist) {
            throw new IllegalArgumentException();
        }

        String sql = "UPDATE user SET name = ? WHERE id = ?";
        jdbcTemplate.update(sql, request.getName(), request.getId());
    }
}

// Controller
  @PutMapping("/user")
  public void updateUser(@RequestBody UserUpdateRequest request) {
    userService.updateUser(jdbcTemplate, request);
  }
// Repository
public class UserRepository {
    // SQL을 사용해 실제 DB와 통신한다
    public boolean isUserNotExist(JdbcTemplate jdbcTemplate, long id) {
        String readSql = "SELECT * FROM user Where id = ?";
        return jdbcTemplate.query(readSql, (rs, rowNum) -> 0, id).isEmpty();

    }

    public void updateUserName(JdbcTemplate jdbcTemplate, String name, long id) {
        String sql = "UPDATE user SET name = ? WHERE id = ?";
        jdbcTemplate.update(sql, name, id);
    }
}


// Service
public class UserService {
    // 현재 유저가 있는지 없는지 확인하고 예외처리를 해주는 부분
    private final UserRepository userRepository = new UserRepository();

    public void updateUser(JdbcTemplate jdbcTemplate, UserUpdateRequest request) {
        boolean isUserNotExist = userRepository.isUserNotExist(jdbcTemplate, request.getId());
        if (isUserNotExist) {
            throw new IllegalArgumentException();
        }

        userRepository.updateUserName(jdbcTemplate, request.getName(), request.getId());
    }
}

이렇게 분리를 했다.
Controller의 하나의 코드를 Controller, Service, Repository로 바꿨다.

현재는 JdbcTemplate를 Repository만 사용하는데 코드는 모든 코드에 있다. 이를 클린하게 바꿔보자.
또한 다른 코드도 위의 삼단 분리를 해보자.
예를 들어 getUser API를 분리해보면 먼저 Repository에 다음과 같이 만들어준다.

public List<UserResponse> getUsers() {
        String sql = "SELECT * FROM user";
        return jdbcTemplate.query(sql, new RowMapper<UserResponse>() {
            @Override
            public UserResponse mapRow(ResultSet rs, int rowNum) throws SQLException {
                long id = rs.getLong("id");
                String name = rs.getString("name");
                int age = rs.getInt("age");
                return new UserResponse(id, name, age);
            }
        });
    }

이걸 이제 UserService에서 이용할 수 있게 해준다.

public List<UserResponse> getUsers(){
        return userRepository.getUsers();
    }

그리고 본 API에서는 /user에서 userService에서 가져오도록 한다.

@GetMapping("/user")
  public List<UserResponse> getUsers() {
    return userService.getUsers();
  }

그런데 Repository에서 JdbcTemplate을 어떻게 가져올수는 없을까?

현재 UserController의 의아한 점은 누가 UserController을 인스턴스화 해주고 있냐는 것이다.
또한, UserController는 JdbcTemplate가 없으면 동작하지 않는다. 근데 우리는 JdbcTemplate를 설정해 준 적이 없는데 어떻게 가져온 것일까?

답은 @RestController에 있다.
UserController 클래스를 스프링 빈으로 등록시킨다.

스프링 빈

스프링 빈이란 무엇일까?
먼저 서버가 실행되면 스프링 서버 내부에 거대한 컨테이너를 만들게 된다. 컨테이너 안에는 여러 클래스가 들어가게 된다. 이 때, 다양한 정보도 함께 들어있고, 인스턴스화도 이루어진다.

스프링은 이러한 빈들을 관리하고 제공하는데, 일반적으로 이들은 스프링 컨테이너에 의해 생성되고 관리된다. 빈은 일반적으로 Java 클래스로 표현되며, 애플리케이션의 다양한 부분에서 사용되는 객체다.

스프링에서 빈은 스프링의 의존성 주입(Dependency Injection) 기능을 통해 구성되며, 이를 통해 빈들 간의 의존성을 관리하고 애플리케이션의 유연성과 테스트 용이성을 높일 수 있다.

UserRepository는 스프링 빈이 아니기 때문에 바로 JdbcTemplate를 가져오지 못한 것이었다.
이를 해결하려면 UserRepository를 빈으로 등록해주면 된다.
@Repository, Service를 통해 어노테이션으로 등록해준다.

이제 다음 순서를 따른다.
먼저 스프링 빈들이 등록이 된다. 그리고 UserRepository가 등록되고 UserService가 등록이 된다. 또한 UserController도 이후에 등록이 된다.

그런데 스프링 컨테이너를 왜 사용하는 걸까?

스프링 컨테이너를 사용하면 컨테이너가 Service를 대신 인스턴스화 하고, 그 때 알아서 Repository를 결정해준다.
컨테이너가 선택해 Service에 Repository를 선택해서 넣어주는 과정을 의존성 주입 (DI, Dependency Injection)라고 한다.

이런 방식을 제어의 역전이라고 한다.

스프링 컨테이너는 객체 간의 의존성을 관리하고 제어한다. 이를 통해 개발자는 객체를 직접 생성하고 관리하지 않고, 스프링 컨테이너에게 객체 생성 및 주입을 위임함으로써 객체 간의 결합도를 낮출 수 있다.

빈을 등록하는 방법

  1. @Configuration

  • 클래스에 붙이는 어노테이션

  • @Bean을 사용할 때 함께 사용해 주어야 한다.

  1. @Bean

  • 메소드에 붙이는 어노테이션

  • 메소드에서 반환되는 객체를 스프링 빈에 등록한다.

댓글을 작성해보세요.

채널톡 아이콘