서비스 추상화
서비스 추상화란
어떤 기능이나 상위기준에서 이해하고 다루기 위해서 세부 사항을 숨기고 추상화하는 것을 말한다. 즉 복잡한 시스템을 관리하기 쉽고 이해하기 쉽게 만들기 위함이다. 단 이몇줄로 이해하기 힘들 것이다. 간단한 코드를 통해서 어떤 식으로 추상화를 사용하는지 알아보자
예시
간단한 카페 등급을 예시를 들어서 생각해보자
카페 등급에는 총 3단계로 이뤄저 있다.
- 초급 단계 : 카페 가입시 기본등급
- 중급 단계 : 게시글 작성 3회 이상, 로그인 횟수 10회 이상
- 고급 단계 : 게시글 작성 10회 이상. 로그인 횟수 20회 이상
이 조건을 만족해야만 해당 등급으로 변경된다.
이런 기능을 추가할때는 DAO에다가 기능을 적용할 수도 있다. 하지만 스프링이 추구하는 "역할과 책임"을 위배하는 코드가 될 수 있다. 그러니 DAO에서는 데이터에 관한 책임을 가져야 하며 서비스 기능적인 부분은 따로 만들어 책임 저야 한다.
추상화하기 전
기본적인 DB에 데이터 저장하는 코드 및 DAO 등은 전체다 설명하면서 하기에는 너무 길어질 수 있어서 생략했으며, XML을 통하여 의존성 주입(DI)을 했습니다.
Grade
public enum Grade {
Advanced(3,null), Intermediate(2,Advanced), Beginner(1,Intermediate);
private final int value;
private final Grade next;
Grade(int value, Grade next){
this.value= value;
this.next =next;
}
public int intValue(){
return value;
}
public Grade nextGrade(){
return this.next;
}
public static Grade valueOf(int value){
switch (value){
case 1: return Beginner;
case 2: return Intermediate;
case 3: return Advanced;
default: throw new AssertionError("Unknown value "+value);
}
}
}
enum타입으로 지정했으며, 값을 db에 저장할 때 에는 상수값으로 저장하기 위해서 intValue() 메서드를 선언해 줬다.
MemberService
public class MemberService {
MemberDao memberDao;
private DataSource dataSource;
//setter 의존성주입
public void add(Member member) {
if(member.getGrade() == null)
member.setGrade(Grade.Beginner);
memberDao.add(member);
}
public void upgradeGrades() throws Exception{
TransactionSynchronizationManager.initSynchronization();
Connection c = DataSourceUtils.getConnection(dataSource);
c.setAutoCommit(false);
try {
List<Member> members = memberDao.getAll();
for (Member member : members) {
if (canUpgradeGrade(member))
upgradeGrade(member);
}
c.commit();
}catch (Exception e){
c.rollback();
throw e;
}finally {
DataSourceUtils.releaseConnection(c, dataSource);
TransactionSynchronizationManager.unbindResource(this.dataSource);
TransactionSynchronizationManager.clearSynchronization();
}
}
private boolean canUpgradeGrade(Member member){
Grade currnetLevel = member.getGrade();
switch (currnetLevel){
case Beginner:
member.setUsername("중급");
return (member.getTitleCount() >=3 && member.getLoginCount()>=10);
case Intermediate:
member.setUsername("고급");
return (member.getTitleCount() >=10 && member.getLoginCount()>=20);
case Advanced: return false;
default: throw new IllegalStateException("Unknown: "+currnetLevel);
}
}
protected void upgradeGrade(Member member){
member.upgradeGrade();
memberDao.update(member);
}
}
- c.setAutoCommit(false); : 트랜잭션을 시작한다는 메서드; (자동 커밋 x)
- upgradesGrades() : 트랙잭션 동기화 방식을 사용한 트랜잭션 적용, 안에 기능적인 코드들은 코드가 길어지면 이해하기가 어려울 수 있어 'canUpgradeGrade()', 'upgradeGrade()'메서드로 추출해 코드의 가독성과 보수성을 높였다.
테스트
member = new Member("1","초급",0,1, Grade.Beginner);
member1 = new Member("2","중급",15,20, Grade.Intermediate);
member2 = new Member("3","고급",10,20, Grade.Advanced);
3개의 회원이 있으며, member1은 조건이 충족되어 변경이 가능하다. upgradesGrades() 메서드를 실행해 보면
다음과 같이 username을 변경된 등급 이름으로 바꾸며 grade등급도 고급으로 바뀐 것을 확인해 볼 수 있다.
이 코드만 보면 괜찮은 코드라고 생각이 들 수 있다. 서비스 기능을 따로 만들어 책임을 분리해 줬다. 하지만 몇 가지 문제점들이 있지만 아직은 하나만 말하겠다. 바로 트랜잭션 부분이다. 만약에 JPA나 Hibernate를 사용하려면 수많은 코드를 고쳐야 한다. 지금은 로컬 트랜잭션을 사용해 한 기술에 종속적인 관계다. 이 문제는 스프링이 추구하는 기능과는 멀어진 코드라고 할 수 있다. 스프링은 변경에 자유로워야 하지만 지금 코드는 변경에 자유롭지 못한 코드다 그래서 우리는 트랜잭션을 추상화하여 변경에 자유로워진 코드로 변경할 예정이다.
추상화 진행
위에서 말한 문제점을 해결해 보겠다.
일단 지금 사용한 트랜잭션은 로컬 트랜잭션이다. 이것은 변경에 자유롭지 못하다 그래서 스프링에 제공하는 트랜잭션 추상화 방법을 사용해 보겠다.
PlatformTransactionmanager
스프링에서 제공하는 추상화, PlatformTransactionmanager는 인터페이스로 설계되어 있으며 여러 기술들을 구현하고 있다.
platformtransactionmanager를 사용하면 변경에 자유로워진다. 그럼 이제 코드를 통해서 확인해 보자.
변경된 upgradesGrades()
public class MemberService {
private PlatformTransactionManager transactionManager;
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
//나머지 코드들은 이전과 동일
//변경된 upgradeGrades()
public void upgradeGrades(){
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
List<Member> members = memberDao.getAll();
for (Member member : members) {
if (canUpgradeGrade(member))
upgradeGrade(member);
}
transactionManager.commit(status);
}catch (Exception e){
transactionManager.rollback(status);
throw e;
}
}
}
변경된 코드를 보면 뭐가 좋아진 줄 모르지만 이전코드보다 훨씬 좋아졌다.
(xml을 통해서 PlatformTransactionManager에 datsource를 주입해줘야 한다.)
첫 번째, 변경에 매우 자유로워졌다. 만약에 다른 기술을 사용하고 싶다면 new Default...() 부분은 교체하면 전과 비교해서 간단하게 변경할 수 있다.
두 번째, 전코드를 보면 fianlly부분이 있을 것이다. 전부 닫아줘야 해서 코드가 간결하지 못했다. PlatformTransactionManager가 관리해 주기 때문에 close() 사용하지 않아도 돼서 코드가 매우 간결해졌다.
세 번째, 단일 책임 원칙을 매우 잘 지킨 코드가 될 수 있다. DAO에 관련된 기능은 memberDAO에 위임했고 memberSerive는 서비스 로직에만 책임을 다하고 있다고 볼 수 있다.
변경을 한 후에 테스트를 해봐도 오류가 나지 않고 변경하기 전과 똑같은 결과가 나올 것이다.
결론
앞서 코드를 통해서 서비스 추상화를 무엇인지 알아봤다.
서비스 추상화를 DAO와 비니지스로직이 담긴 부분을 확실히 분리하여 역할과 책임을 적용하고, DI를 통해서 변경에 자유로워야 한다.
'Spring > spring' 카테고리의 다른 글
[스프링] AOP(Aspect-Oriented-Programming)이해 (0) | 2023.11.22 |
---|---|
[스프링] 트랜잭션(Transaction)이란 (0) | 2023.11.22 |
스프링 예외처리(Exception) (0) | 2023.11.17 |
스프링 오브젝트 와 의존관계 (1) | 2023.11.14 |
스프링이란? (0) | 2023.05.28 |