스프링을 계속 공부하던 중 jsp 관련 과제 질문을 받아서
당연하게 스프링을 사용하는 줄 알고 userRepository, BoardRepository 둘다 Service로 가져와서 쓰면 된다는 답을 하였다.
답을 하던중 스프링을 사용하지 않으면 저 둘을 같이 사용하지 못하고 두 테이블을 join해야만 하나? 라는 생각이 들었다.
하지만 명확하게 답을 모르겠어서 정리해보려한다.
이런 기본적 개념조차 명확하게 설명 못하는 상황.. 공부가 시급하다.
궁금증1 스프링 빈 등록없이 사용 하면?
분명 안될 거 같진 않았는데, 역시 코드로 돌려보니 빈 없이도 수동으로 의존성 주입하여 작동하였다.
private final BoardRepository boardRepository;
private final UserRepository userRepository;
public TestService(BoardRepository boardRepository, UserRepository userRepository){
this.userRepository = userRepository;
this.boardRepository = boardRepository;
}
위와 같이 Service에 의존성을 주입을해 줬고
public String getBoard(Long id){
Board board = boardRepository.findId((id));
User user = userRepository.findId((id));
String b_id = board.getId().toString();
String b_r = board.getRes();
String u_n = user.getName();
String reVal = b_id + "/" + b_r + "/" + u_n;
return reVal;
}
이런식으로 간단한 예시 메서드를 구현 했다.
UserRepository userRepository = new UserRepository();
BoardRepository boardRepository = new BoardRepository();
TestService testService = new TestService(boardRepository, userRepository);
//user 생성
String uname = "test";
User user1 = testService.createUser(uname);
//board 생성
String res = "testres";
Board board1 = testService.createBoard(res);
//board 조회
Long findId = Long.valueOf(1);
String val = testService.findBoard(findId);
System.out.println(val);
test결과는 잘나왔다
-> spring 없이도 의존성 주입 방식으로 코드 작성 가능하다
-> spring 안쓴다고 서로 다른 테이블 정보 가져올때 꼭 join을 할 필요는 없다. (복잡해지면 join으로 가져오는게 깔끔하다고 한다.)
궁금중2 그럼 스프링 쓰는이유?
물론 지금 까지 이론으로 스프링 쓰는 이유는 계속 들었지만.. 위 코드를 짜고 보니 그렇게 안힘든데? 그냥 이렇게 쓰면 안되나? 라는 생각이 들었다. (무식하면 용감하다..)
강의로 공부할때 다 설명 들었던 내용일건데... 다시 정리 해보자
강의 예시는 먼저 인터페이스화를 하여 설명한다.
일단 위 코드에 상황을 추가해보겠다. board를 조회하는데 문자열 맨앞에 hello를 붙이는 기능을 추가한다.
그리고 다른 기능으로 변경하고 할 수도 있으니 인터페이스와 구현체를 통해 만들겠다.
public interface HeaderSetPolicy {
String helloSet(String header1);
}
인터페이스를 만들어주고 (반환 값에 인사말 더해주는)
public class Header1 implements HeaderSetPolicy {
@Override
public String helloSet(String header1) {
return "hello" + header1;
}
}
인터페이스를 상속 받아 구현체를 만들었다. (hello + 파라미터 값으로 인사말 추가하는)
public class TestService {
private final BoardRepository boardRepository = new BoardRepository();
private final UserRepository userRepository = new UserRepository();
private final HeaderSetPolicy headerSetPolicy = new Header1();
public String findBoard(Long id){
Board board = boardRepository.findId((id));
User user = userRepository.findId((id));
String b_id = board.getId().toString();
String b_r = board.getRes();
String u_n = user.getName();
String header = headerSetPolicy.helloSet("인사말 입니다.");
String reVal = header + b_id + "/" + b_r + "/" + u_n;
return reVal;
}
그리고 서비스에서 이렇게 사용해 줬다. (board 문자열 앞에 인사말(header) 추가 해주는 기능)
그럼 객체지향 관점에서 이 코드의 문제점을 알아보자
-> service가 인터페이스 뿐만아닌 구현체인 Header1에 동시에 의존하고있다. (DIP위반)
-> 새로운 구현체 Header2 로 변경하려면 service의 코드를 수정해야한다. (OCP위반)
이 문제를 해결하기 위해
private final HeaderSetPolicy headerSetPolicy = new HeaderSetPolicy();
위와 같이 인터페이스에만 의존하도록 의존관계를 바꿔주면 당연히 구현체를 못 찾아 에러가난다.
이를 해결하기 위해 스프링의 중요한 개념인 관심사 분리가 나온다.
공연을 통해 예시를 들어보면 (강의에서 알려주신)
- 공연에는 각각 정해진 배역(인터페이스가)이 있고, 그 배역을 수행하는 배우(구현체)가 있다.
- 각 배우는 상대 배역의 배우가 누구든 똑같이 정해진 공연을 수행한다.
- 담당 배우를 섭외하고, 역할에 맞는 배우를 지정하는것은 공연 기획자가 한다.
현재 문제가 있는 위 코드는
배역인 인터페이스(HeaderSetPolict) 를 연기하는 배우인 구현체가 바뀌게 되면서 전체 공연인 service가 변경되는 상황인 것이다.
이는 배역을 지정할 공연 기획자가 없어서이다.
그럼 공연 기획자 역할인 AppConfig를 만들어 보겠다.
***
이제 보니 위에 맨처음 코드는 appConfig의 역할을 테스트 코드에서 해주고 있다 (기능 변경시 클라이언트 변경 최악 방법인 듯)
지금 코드에선 서비스 내에서 하나씩 의존성을 주입하고
맨 처음 코드는 service에서 아래 설명할 appConfig 개념을 살짝 가져와 생성자로 의존성을 주입해준 것이다.
***
public class AppConfig {
public TestService testService(){
return new TestService(new BoardRepository(),new UserRepository(), new Header1());
}
}
AppCofig를 이렇게 만들어주고
public TestService(BoardRepository boardRepository, UserRepository userRepository, HeaderSetPolicy headerSetPolicy){
this.userRepository = userRepository;
this.boardRepository = boardRepository;
this.headerSetPolicy = headerSetPolicy;
}
서비스에는 생성자로 의존성을 주입 해준다.
AppConfig appConfig = new AppConfig();
TestService testService = appConfig.testService();
Long findId = Long.valueOf(1);
String val = testService.findBoard(findId);
System.out.println(val);
테스트는 다음과 같이 해준다.
이렇게 해놓고 다시 객체지향 관점에서 보자.
-> Servicee는 인터페이스인 HeaderSetPolicy에만 의존하고 있다.
-> HeaderSetPolicy 구현체를 바꿔줄 때 공연 기획자 즉 AppConfig만 바꿔주면 된다.
Service는 어떤 구현체가 들어올지 신경 안쓰고 실행 역할에만 집중하게 된다.
결론
appConfig를 사용했지만 di를 활용하여 객체지향적으로 만들어 졌을 뿐 여전히 순수 자바 코드이다.
이를 스프링으로 변환하여 사용하는데 글이 길어져 다음 글에 계속 하겠다.
이 글은 스프링은 전혀 나오지 않은 di개념 정리 정도가 되어버린것 같다..ㅎ
'Spring' 카테고리의 다른 글
| [spring] 스프링 login (0) | 2024.02.21 |
|---|---|
| [스프링] 스프링빈 사용이유 (0) | 2024.01.13 |
| [프로젝트] dto, mapper 개념 (0) | 2023.10.27 |
| [스프링] Security (0) | 2023.08.05 |
| [스프링] 트랜잭션 (0) | 2023.08.04 |