-
토비의 스프링 - 1권 5장 서비스의 추상화BackEnd/SpringBoot 2023. 10. 5. 00:27
1. 추상화 :
하위 시스템의 공통을 뽑아내서 분리하는 것을 말한다 로우 레벨의 코드를 알지 못하게 하여 일관된 방법으로 접근이 가능하다.
(쉽게 말해서 우리는 정해진 형태의 리모컨을 만들어서 필요할때 껍데기는 두고 내용물만 바꿔 기능을 수행하고자 한다는 것이다. 클라이언트는 리모컨의 버튼이 동작하는 세세한 정보를 정확히 알고 써야할 필요가 없기때문이다.)
자바 라이브러리 중에 목적이 유사한 기술들이 있고 이런 기술들은 환경과 상황에 영향을 받는다면 매번 바뀌어야하는 번거로움이있다.
이런 유사한 기술들을 일관된 방법으로 사용하도록 하도록 유도한다면 개발에 큰 이점으로 다가온다.
대표적으로 트랜잭션을 다루는 기술로 알 수있다.
2. 트랜잭션 서비스 추상화 :
예시 문제 : 전체 유저 레벨업 도중 에러 발생 시 전부 롤백할때 (일부 유저만 성공시 차별로 오해 할 수있기때문에 오류 시 전체 롤백)
방법 : 트랜잭션 경계 설정을 명확하게 한다.
트랜잭션 경계설정에 다양한 하이버네이트, JDBC 또는 로컬 트랜잭션(하나의 디비 커넥션 안에 트랜잭션)이 아닌 다양한 디비를 다루는 JTA를 사용시
매번 각기 다른 기술로 트랜잭션으로 생성하는 것이 아닌 우리가 알고있는 공통적으로 구현된 조상 인터페이스를 이용하여 트랜잭션을 취급하는 것이다.
로컬 트랜잭션 사용한 코드 구조
public void upgradeLevels() {
Connection connection = DataSourceUtils.getConnection(datasource);
connection.setAutoCommit(false);
try {
// DB 작업 진행
}
connection.commit();
} catch (Exception e) {
connection.rollback();
}
}JTA를 이용한 트랜잭션 코드 구조
public void upgradeLevels() {
InitialContext ctx = new InitialContext();
UserTransaction tx = (UserTransaction)ctx.lookup(USER_TX_JNDI_NAME);
tx.begin();
try {
// DB 작업 진행
}
tx.commit();
} catch (Exception e) {
tx.rollback();
}
}위 두가지 형태를 추상화하여 만든 코드 구조
private PlatformTransactionManager transactionManager;
public void setTransactionManager(PlatformTransactionManager platformTransactionManager) {
this.transactionManager = transactionManager;
}
public void upgradeLevels() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// DB 작업 진행
}
this.transactionManager.commit(status);
} catch (Exception e) {
this.transactionManager.rollback(status);
}
}이러한 기술과 환경에 최소화한 코드를 포터블한 코드라고 할 수 있다.
주의 디비 커넥션과 트랜잭션은 다르다.
디비 커넥션이 시작 되면 트랜잭션 시작을 하고 커밋이나 롤백이 되는 시점까지를 트랜잭션 경계 설정 범위이다.
디비 커넥션이 클로즈 될때까지 범위를 디비 커넥션의 범위다.3. 코드 개선시 중점 사항 :
1. 코드에 중복된 부분은 없는가?
2. 코드가 무엇을 하는 것인지 이해하기 불편하지 않은가?
3. 코드가 자신이 있어야 할 자리에 있는가?
4. 앞으로 변경이 일어난다면 어떤 것이 있을 수 있고, 그 변화에 쉽게 대응할 수 있게 작성되어 있는가?
코드 개선 사항 예시 :
트랜잭션 로직, 유저 정보 조회, 등급업 조건 로직, 등급업 로직이 한꺼번에 존재한다.
public void upgradeLevels() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
List<User> users = userDao.getAll();
for (User user : users) {
Boolean changed = null;
if (user.getLevel() == Level.Basic && user.getLogin() >= 50) {
user.setLevel(Level.SILVER);
changed = true;
} else if (user.getLevel() == Level.SILVER && user.getRecommand() >= 30) {
user.setLevel(Level.GOLD);
changed = true;
}
//else if (use.getLevel() ==.......){
//}
}
this.transactionManager.commit(status);
} catch (Exception e) {
this.transactionManager.rollback(status);
}
}기존에 있던 등급 조건을 별도의 메서드로 분리, 업그레이드 메서드도 별도 분리, 추후 프록시 패턴을 통해 트랜잭션 로직 또한 분리할 수 있다.
관심사를 최대한 분리 시키고 단일 책임의 원칙을 적용한다면 하나의 모듈이 바뀌는데 수정부분이 줄어들고 수정 대상이 명확해진다.
public void upgradeLevels() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
List<User> users = userDao.getAll();
for (User user : users) {
if (canUpgradeLevel(user)) {
upgradeLevel();
}
}
this.transactionManager.commit(status);
} catch (Exception e) {
this.transactionManager.rollback(status);
}
}
public boolean canUpgradeLevel(User user) {
Level currentLevel = user.getLevel();
switch (currentLevel) {
case BASIC:
return (user.getLogin() >= 50);
case SILVER:
return (user.getRecommend() >= 30);
}
}4. 정리 :
어플리케이션의 낮은 결합도는 로우레벨의 기술로부터 독립시켜 주며 자유롭게 확장 할 수있는 구조로 DI에 중요한 역할을 한다.
단일 책임의 원칙은 하나의 모듈이 바뀌는데 하나의 책임이 필요하며 코드가 수정될때 한부분만 수정하면 되기 때문에 수정 대상이 명확해진다.
'BackEnd > SpringBoot' 카테고리의 다른 글
[스프링부트] ./gradlew build시 에러 -> error: cannot find symbol (0) 2023.11.16 토비의 스프링 - 1권 6장 AOP (0) 2023.10.08 토비의 스프링 - 1권 4장 예외 (0) 2023.09.13 토비의 스프링 - 1권 3장 템플릿 (0) 2023.08.23