전공공부
[Spring] PSA(Portable Service Abstraction) + DI(Dependency Injection) 본문
[Spring] PSA(Portable Service Abstraction) + DI(Dependency Injection)
monitor 2024. 1. 19. 17:09PSA (Portable Service Abstraction)
PSA (Portable Service Abstraction)는 스프링에서 서비스 추상화를 구현하는 개념입니다. 이는 특정 기술에 종속되지 않고, 일관된 인터페이스를 제공하여 서비스들을 편리하게 교체하고 테스트하기 위한 목적으로 도입되었습니다.
그렇다면 이 예제가 무엇일까요?
import com.test.tester.portableServiceAdapter.dao.UserDao;
import com.test.tester.portableServiceAdapter.dto.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class UserDaoImpl implements UserDao {
private final JdbcTemplate jdbcTemplate;
public UserDaoImpl(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void createUserTable() {
jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS users (id SERIAL, name VARCHAR(255))");
}
@Override
public void insertUser(User user) {
jdbcTemplate.update("INSERT INTO users (name) VALUES (?)", user.getName());
}
@Override
public User getUserById(Long id) {
return jdbcTemplate.queryForObject("SELECT * FROM users WHERE id = ?", new Object[]{id}, (rs, rowNum) ->
new User(rs.getLong("id"), rs.getString("name")));
}
}
public interface UserDao {
void createUserTable();
void insertUser(User user);
User getUserById(Long id);
}
위 처럼 Dao 클래스 구현체를 직접 구현해서 쓰는 것 보다 인터페이스 하나 잘 만들어서 가져와서 쓰고 의존성 주입을 해준다면 편리하게 우리는 쓸 수 있습니다. 그리고, 이미 JPA와 같이 추상화 된 것들에 의해서 이미 코드들이 잘 작동하고 있습니다. 예를 들면 우리는 직접 @Transactional을 구현 할 필요 없이 low level에서 잘 구현 된 것을 우리는 어노테이션을 불러서 사용하고 있죠.
(사실 위 코드도 완전히 추상화 된 것을 쓰려면 JPA로 구현 된 것을 그대로 쓰는 것이 좋겠지만 위와 같이 사용하겠습니다.)
또한, PSA로 잘 인터페이스를 둔 것 단위테스트가 쉽다는 장점이 있는데요.
아래 예시를 보시죠
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
@MockBean
private UserDao userDao;
@Test
public void testUserService() {
// Mock 객체를 이용하여 userDao의 행동 정의
when(userDao.getUserById(1L)).thenReturn(new User(1L, "Mock User"));
// UserService의 특정 메서드 호출
User retrievedUser = userService.getUserById(1L);
// 결과 검증
assertThat(retrievedUser).isNotNull();
assertThat(retrievedUser.getName()).isEqualTo("Mock User");
}
}
만일, 서비스 단에서 복잡한 DB 접근 구조를 모두 구성하고 만들어야 한다면 우리는 DB 단의 구성까지도 모두 알아서 구현하고 호출하는 코드를 짜야겠지만 위와 같이 간단하게 단위테스트를 실행 할 수 있습니다.
위 코드는 제어권이 상위 템플릿으로 역전이 적절히 잘 되었고 (IoC) 의존성 주입을 interface로 실행하여 만일 다른 객체를 의존하는 코드를 만들었을때 그 객체가 바뀜으로 인해서 일어나는 일의 변화에 대해서 적절한 대응이 가능한 코드가 되었습니다.
추가로 만일 DI를 제대로 하지 않으면 아래 처럼 나오게 됩니다.
DI (Dependency Injection)
public class UserService {
private UserRepository userRepository;
public UserService() {
// 이렇게 직접 인스턴스를 생성하는 것은 의존성 주입을 지키지 않는 것입니다.
this.userRepository = new UserRepository();
}
// UserService의 나머지 로직...
}
public class UserRepository {
// UserRepository의 로직...
}
이따위의 코드가 나오게되고 실제로 이는 UserRepository의 생성자에서 생성하는 것이 바뀔때 마다 의존적입니다.
public class UserService {
private UserRepository userRepository;
// 생성자를 통해 UserRepository를 주입받도록 변경
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// UserService의 나머지 로직...
}
public class UserRepository {
// UserRepository의 로직...
}
이를 내 객체에서 의존성을 주입을하게 되면 의존성을 낮출 수 있습니다.
참고로, 스프링에서 추천하는 방식은 인터페이스 implementaion 보다는 생성자를 통해서 생성하는 방식을 권하고 있습니다.
+) 실제로 이 포스팅을 보시는 여러분들이 SI 기업에 취직하게 된다면 간간히 이런 기본적인 룰을 지키지 않고 실행하는 경우가 종종 있습니다. 그럴때마다 왜 그것을 써야 하는지 정확하게 알고 이걸 쓸 때의 장점을 잘 설명을 할 수 있어야 좀 더 세련된 코드를 구현 할 수 있습니다. 하지만, 기업 특성상 업무 룰이 먼저가 되기 때문에 따로 개인 레포를 만들거나 프로젝트를 진행하면서 세련된 코드를 짜는 연습을 해야 할 것 입니다.
위 코드를 참조하려면 아래 링크를 참고하면 됩니다.
https://github.com/1ComputerMaster/1ComputerMaster/tree/main/Study/tester
'Study > Spring Boot' 카테고리의 다른 글
[Webflux] Webflux, MVC, Virtual Thread...? (0) | 2024.01.27 |
---|---|
[Spring] Hexagonal Arichtecture - MVC 패턴과 비교하다. (0) | 2024.01.21 |
Mockito를 사용한 단위 테스트에서 발생하는 다양한 이슈 (0) | 2024.01.20 |
[Hexagonal Architecture] 1장. 왜 헥사고날 아키텍쳐를 사용하는가? (0) | 2023.12.09 |
Spring Batch를 이용한 대량의 insert 최적화 (0) | 2023.01.03 |