서버 개발 발 들이기 6

바람찬허파·2023년 9월 22일
0
post-thumbnail

기존 코드에 대한 의문점

UserController는 JdbcTemplate을 바로 사용?

기존 UserService, UserRepository는 JdbcTemplate을 사용하기 위해서 의존성 주입을 사용하였다. 그런데 UserRepository는 그러지 않아도 된다?

build.gradle을 통해 dependency(의존성)을 설정하였으며,
의존성에 의해 JdbcTemplete이 스프링 컨테이너 안에 들어가게 된 것이다.

의존성이 특정 라이브러리, 프레임워크를 가져온다.
스프링 컨테이너는 필요한 클래스를 연결해준다.

UserRepository는 스프링 빈이 아니기에 -> JdbcTemplate를 바로 가져올 수 없는 것이다.

스프링 컨테이너가 왜 필요한가?

총 3단계를 통해 코드를 발전시켜가며, 스프링 컨테이너가 필요한 이유에 대해 알아보도록 하자.

문제 배경


책 이름을 받아 이를 저장하고자 한다. 다만, 책 데이터를 메모리 / MySql DB에 저장할지 정해지지 않았다.

1. JAVA

1-1. 메모리에 저장하는 경우

BookController -> BookService -> BookRepository 세 클래스가 다음과 같은 의존관계를 가진다.

package com.group.libraryapp.controller.book;

@RestController
public class BookController {
    private final BookService bookService = new BookService();

    @PostMapping("/book")
    public void saveBook(@RequestParam String title){
        bookService.saveBook(title);
    }
}
package com.group.libraryapp.service.book;

public class BookService {
    private final BookRepository bookRepository = new BookRepository();

    public void saveBook(String title){
        bookRepository.saveBook(title);

    }
}
package com.group.libraryapp.repository.book;

import com.group.libraryapp.domain.book.Book;

import java.util.ArrayList;
import java.util.List;

public class BookRepository {
    private final List<Book> books = new ArrayList<>();

    public void saveBook (String title){
        Book book = new Book(title);
        books.add(book);
        for (int i = 0 ; i < books.size(); i++){
            System.out.println("title = " + books.get(i).getTitle());
        }
    }
}

자바 코드만 가지고 '메모리에 저장하는 경우'의 코드이다.
만약, 회사 내에서 메모리에 저장하는 것이 아닌 (당연하지) DB에 저장한다고 가정하자.
mySQL 내 book 테이블에 저장하는 BookMySqlRepository 클래스를 생성한다.

1-2. MySql DB에 저장하는 경우

package com.group.libraryapp.controller.book;
//...
@RestController
public class BookController {
    private final JdbcTemplate jdbcTemplate;
    private final BookService bookService;

    public BookController(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
        bookService = new BookService(jdbcTemplate);
    }

    @PostMapping("/book")
    public void saveBook(@RequestParam String title){
        bookService.saveBook(title);
    }
}
package com.group.libraryapp.service.book;

public class BookService {
    private final JdbcTemplate jdbcTemplate;
    private final BookMysqlRepository bookRepository;

    public BookService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
        // this.bookRepository = new BookRepository(jdbcTempalte); 메모리에 저장하는 클래스
        this.bookRepository = new BookMysqlRepository(jdbcTemplate);
    }

    public void saveBook(String title){
        System.out.println("title = " + title);
        bookRepository.saveBook(title);
    }

}
package com.group.libraryapp.repository.book;

public class BookMysqlRepository {
    private final JdbcTemplate jdbcTemplate;

    public BookMysqlRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void saveBook(String title){
        String sql = "INSERT INTO book (title) VALUES (?)";
        jdbcTemplate.update(sql, title);
    }
}


해당 코드를 작성하며 JdbcTemplate 의존성 주입과 관련한 에러가 발생했었다. 그에 대한 정리글을 아래와 같다
의존성 주입 에러 정리글_서버 개발 발 들이기 5

1-3. 문제점

해당 코드를 살펴보면, 메모리에 저장하는 경우 / DB에 저장하는 경우에 따라 코드가 변경된다.
중요한 것은 Service의 코드가 변경된다는 것이다.

private final JdbcTemplate jdbcTemplate;
	// private final BookRepository bookRepository;
    private final BookMysqlRepository bookRepository;

    public BookService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
        // this.bookRepository = new BookRepository(jdbcTempalte); 메모리에 저장하는 클래스
        this.bookRepository = new BookMysqlRepository(jdbcTemplate);
    }

이렇게 BookRepository의 종류가 변경된다면, 그 객체를 생성하는 코드를 모두 변경해주어야 한다. 이처럼 비효율적이라니!

2. Interface

다음으로는 Interface를 통해 BookRepository 인터페이스를 implement 하는 두 클래스를 생성한다.

//Service
package com.group.libraryapp.service.book;

public class BookService {
    private final JdbcTemplate jdbcTemplate;
    private final BookRepository bookRepository;

    public BookService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
        this.bookRepository = new BookMemoryRepository(jdbcTemplate);
    }

    public void saveBook(String title){
        System.out.println("title = " + title);
        bookRepository.saveBook(title);
    }
}
//Interface
package com.group.libraryapp.repository.book;

public interface BookRepository {
    public void saveBook(String title);
}
package com.group.libraryapp.repository.book;

public class BookMemoryRepository implements BookRepository{
    private final List<Book> books = new ArrayList<>();
    private final JdbcTemplate jdbcTemplate;

    public BookMemoryRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public void saveBook (String title){
        Book book = new Book(title);
        books.add(book);
        for (int i = 0 ; i < books.size(); i++){
            System.out.println("title = " + books.get(i).getTitle());
        }
    }
}
package com.group.libraryapp.repository.book;

public class BookMysqlRepository implements BookRepository{
    private final JdbcTemplate jdbcTemplate;

    public BookMysqlRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public void saveBook(String title){
        String sql = "INSERT INTO book (title) VALUES (?)";
        jdbcTemplate.update(sql, title);
    }
}

BookRepository Interface를 구현하는 메모리에 저장하는 BookMemoryRepository와 DB에 저장하는 BookMysqlRepository 클래스를 생성하였다.
Service에서는 타입은 동일하게 BookRepository이므로 구현체만 변경하면 된다.

 private final JdbcTemplate jdbcTemplate;
    private final BookRepository bookRepository;

    public BookService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
        //this.bookRepository = new BookMemoryRepository(jdbcTemplate);
        this.bookRepository = new BookMysqlRepository(jdbcTemplate);
    }
    


두 구현체 모두 title 데이터를 받아 Book 객체를 생성한 뒤 저장하는 것을 확인하였다.
🚨 그럼에도 문제는 여전하다.
여전히 BookService에서는 객체 생성시 어떤 클래스를 사용하는지 선택해야 하며, 코드를 모두 수정해주어야 한다.
그렇다면, 과연 코드를 수정하지 않고도 구현체를 선택할 수 있을까?

3. Spring Container

앞선 의문에 대한 답변은 Repository, Service를 스프링 빈으로 등록한다!

@Service, @Repository 어노테이션을 통해 스프링 컨테이너에 스프링 빈으로 등록한다. 그렇다면 서버가 시작되면, 다음 과정이 이루어진다.
1> 서버 시작되면 스프링 컨테이너가 생성된다.
2> 기본적으로 많은 스프링 빈들이 등록된다.
3> 개발자가 설정해 둔 스프링 빈이 등록된다.
4> 이 때 필요한 의존성이 자동으로 설정된다.

Primary

해당 어노테이션을 통해서 우선순위를 설정할 수 있다.

0개의 댓글