백기선님이 예전에 올리신 영상을 보는데.
지식이 부족하여 처음부터 끝까지 이해가 안갔다.
영상의 내용과 댓글을 참고하여 관련된 키워드를 검색하고 정리 해보고자 한다.
[문제]
- 아래 RepositoryRank 클래스의 getPoint 메소드에 대한 유닛 테스트를 작성한다고 해보자.
- GitHub.connect()라는 스태틱 메소드를 호출하고 있는데 이걸 Mock Framework 없이 Mocking 할 수 있는 구조로 개선하려면 어떻게 해야 될까?
import org.kohsuke.github.*;
import java.io.IOException;
public class RepositoryRank {
public int getPoint(String repositoryName) throws IOException {
GitHub github = GitHub.connect();
GHRepository repository = github.getRepository(repositoryName);
int points = 0;
if (repository.hasIssues() ) {
points += 1;
}
if (repository.getReadme() != null) {
points += 1;
}
if (repository.getPullRequests(GHIssueState.CLOSED).size() > 0) {
points += 1;
}
points += repository.getStargazersCount();
points += repository.getForksCount();
return points;
}
public static void main(String[] args) throws IOException {
RepositoryRank spring = new RepositoryRank();
int point = spring.getPoint("whiteship/live-study");
System.out.println(point);
}
}
// https://github.com/hub4j/github-api/blob/main/src/main/java/org/kohsuke/github/GitHub.java
package org.kohsuke.github;
public class GitHub {
...
/**
* Obtains the credential from "~/.github" or from the System Environment Properties.
*
* @return the git hub
* @throws IOException
* the io exception
*/
public static GitHub connect() throws IOException {
return GitHubBuilder.fromCredentials().build();
}
...
}
개선 힌트는 스프링 강의나 토비의 스프링 책에서 말했던 Portable Service Abstration이라는 개념을 말하고 있다. 즉 테스트하기 힘든 코드를 테스트하기 편리한 구조로 바꾸기 위해 추상화를 해야 한다는 게 골자다.
import org.kohsuke.github.*;
import java.io.IOException;
public class RepositoryRank {
interface GithubService {
GitHub connect() throws IOException;
}
class GithubServiceImpl implements GithubService {
@Override
public GitHub connect() throws IOException {
return GitHub.connect();
}
}
private GitHubService githubService;
public RepositoryRank(GitHubService githubService) {
this.githubService = githubService;
}
public int getPoint(String repositoryName) throws IOException {
GitHub github = githubService.connect();
GHRepository repository = github.getRepository(repositoryName);
int points = 0;
if (repository.hasIssues() ) {
points += 1;
}
if (repository.getReadme() != null) {
points += 1;
}
if (repository.getPullRequests(GHIssueState.CLOSED).size() > 0) {
points += 1;
}
points += repository.getStargazersCount();
points += repository.getForksCount();
return points;
}
public static void main(String[] args) throws IOException {
RepositoryRank spring = new RepositoryRank(new GithubServiceImpl());
int point = spring.getPoint("whiteship/live-study");
System.out.println(point);
}
}
위와 같이 GitHub.connect()라는 static method를 호출하는 부분, 즉 GitHub 커넥션을 가져오는 부분을 추상화하여 그것을 DI를 통해 런타임 시 결정하게 하자는 것이 답이었다.
1. Spring PSA (Portable Service Abstraction)
스프링의 핵심가치 3가지(IoC / DI, AOP, PSA) 중 하나입니다.
@Transactional 어노테이션을 선언하는 것 만으로 별도의 코드 추가 없이 트랜잭션 서비스를 사용할 수 있으며 내부적으로 트랜잭션 코드가 추상화되어 숨겨져 있는 것과 같이 추상화 계층을 사용하여 어떤 기술을 내부에 숨기고 개발자에게 편의성을 제공해주는 것이 서비스 추상화(Service Abstraction)입니다.
그리고 DB에 접근하는 방법은 여러가지가 있습니다. 기본적으로 Jdbc를 통해 접근(DatasourceTransactionManager)할 수 있으며 ORM을 이용하고자한다면 JPA(JpaTransactionManager)를 통해서 접근할 수도 있습니다. 어떠한 경우에도 @Transactional 어노테이션을 이용하면 트랜잭션을 유지하는 기능을 추가할 수 있습니다. 이렇게 하나의 추상화로 여러 서비스를 묶어둔 것을 Spring에서 Portable Service Abstraction이라고 합니다.
Spring의 @Transactional은 각 TransactionManager를 각각 구현하고 있는 것이 아니라 최상위 PlatformTransactionManager를 이용하고 필요한 TransactionManager를 DI로 주입받아 사용하는구나라는 사실을 알 수 있습니다.
PlatformTransactionManger의 하위로 다양한 TransactionManager가 구현되어있습니다. 아래 코드는 Transaction을 제어함에 있어 하나의 기술에 국한되지 않고 트랜잭션관리의 모든 기술을 아우를 수 있는 Spring의 PSA(Portable Service Abstraction)에 맞는 코드라고 볼 수 있습니다.
private PlatformTransactionManger transactionManager;
public constructor(PlatformTransactionManger transactionManager) { // 생성자
this.transactionManager = transactionManager; // 해당 주입 instance의 변경으로 JPA, hibernate, JDBC로 쉽게 변경 가능.
}
public void method_name() throw Exception {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// 3. DB 쿼리 실행
transactionManager.commit(status);
} catch(Exception e) {
transactionManager.rollback(status);
throw e;
}
}
2. IoC / DI
3. DIP
4. Mocking
참고
'Spring' 카테고리의 다른 글
리소스 폴더 내 파일 저장 및 불러오기 (0) | 2022.10.26 |
---|---|
[Spring] could not find or load main class 해결 (0) | 2022.09.07 |
[Speing] intellij Database Navigator mysql (0) | 2022.09.02 |
[Spring] H2 데이터베이스 사용하기 & 인텔리제이 DB도구 추가 (0) | 2022.08.26 |
[Spring] 인텔리제이 swagger 에러 해결 (0) | 2022.08.23 |
댓글