본문 바로가기

스프링

[Spring] 서비스 추상화_역할위임(Enum)과 상수

*  서비스추상화1의 문제 > DB의 모든 사용자의 레벨을 변경해 주는 로직

for루프 안에 if/else 블록들이 읽기 불편하고 코드가 깔끔하지 않다.

이렇게 깔끔해 보이지 않는 이유는 다른 여러가지 로직이 한데 섞여 있다.

-       18.user.getLevel()==Level.BASIC 레벨이 무엇인지 파악하는 로직

-       19.user.getLogin()>=50 업그레이드 조건을 담은 로직

-       20. Chaged = true : 의미 없고 30의 작업을 위함

레벨이 늘어나면 for 루프안에 if 조건이 계속 추가되야함(지저분해짐)

변화에 취약하고 다루기 힘든 코드가됨

 

 

*  서비스추상화2의 문제 > 공통과 변하는 로직 메소드분리

14~21. 추상적인 로직 : 모든 사용자 정보를 가져와 한명씩 레벨 업그레이드. 참 간단하다.

18. upgradeLevel() : 어떤 작업을 하는지 쉽게 이해할 수 있다.

28~41. 자주 변경될 가능성이 있는 구체적인 로직

28. canUpgradeLevel() : 주어진 user에 대해 업그레이드가 가능하면 true, 그렇지 않으면 false. 역할과 책임이 명료해진다. GOLD 같은 경우 항상 업그레이드가 불가능하니 false, 그외에는 사용자 정보를 기준으로 조건을 넣으면 된다. 처리할 수 없는 레벨인 경우에 예외를 던져주면 새로운 레벨이 추가되었을 때 예외가 발생할 테니 쉽게 확인할 수 있다.

37. upgradeLevel() : 다음 레벨로 바꿔주고 DB에 업데이트를 한다. 물론 나중에 업그레이드 안내메일을 보낸다거나, 로그를 남기거나, 관리자에게 통보를 해주는 작업이 추가될 수도 있다. 이때 메소드를 분리해두면 나중에 작업이 추가되더라도 어느 곳을 수정해야 하는지 명확해진다는 장점이 있다.

하지만, 37. upgradeLevel()에 다음 레벨이 무엇인지 너무 노골적으로 들어가 있고, 예외 상황에 대한 처리가 없으며, GOLD레벨 사용자가 업그레이드시 DAO의 업데이트 메소드만 실행될 것이다. 또한 레벨이 늘어나면 if문이 점점 길어질 것이고, 레벨 변경거나 레벨이 늘어나면 사용자 오브젝트에서 if문이 복잡해지고 늘어날 것이다.

 

 

 

앞선 1편에서 서비스의 복잡도를 위해

6. 레벨의 순서와 다음 레벨이 무엇인지 결정하는 일은 Level에게 위임. 이부분을 Service에 담아둘 이유가 없다.

6, 10. 생성자 파라미터를 통해 다음레벨에 대한 정보를 함께 넣어준다.

17. 다음 레벨에 대한 정보를 얻기 위한 메소드.

* 이렇게 함으로 인해 level 이늄 안에서 레벨 업그레이드 순서를 관리할 수 있어, Service에서 if 조건식을 통해 비즈니스 로직에 담아둘 필요가 없어진다.

 

Exception을 이런 식으로 필요에 따라 런타임 혹은 checked 예외로 전환하여 던지면됨

 

User의 내부 정보가 변경되는 것은 Service보다 User가 스스로 다루는게 적절하다. 단순 자바빈이긴 하지만, User도 엄연한 자바 오브젝트이고, 내부 정보를 다루는 기능이 있을 수 있다. 서비스 로직에서 일일이 User의 필드를 수정하는 로직을 갖고 있기 보다는, User에게 레벨 업그레이드 해야하니 정보를 변경하라고 요청하는 편이 낫다.

23. Level에게 다음 레벨이 무엇인지 알려달라고 요청해서 현재 레벨을 변경해 주면 된다.

25. , 업그레이드가 불가능한 경우 Service에서 가능여부를 판단할 수도 있지만, UserUserSerice만 사용하는 것이 아니므로 스스로 예외상황에 대한 검증 기능을 갖고 있는편이 안전하다.

28. 가장 최근에 레벨을 변경한 일자를 알고 싶을 때 이런식으로 추가해주면된다.

 

User는 현재 간단하지만, 로직이 추가될 가능성이 있으니 테스트를 만들어두면 도움이 된다.

20. Level 이늄에 정의된 모든 레벨을 가져와서 User에 설정해두고 UserupgradeLevel을 실행해서 다음 레벨로 바뀌는지 확인한다. 굳이 이렇게까지 테스트해야할까 싶지만, 좀더 복잡한 기능이 추가시 이 테스트를 확장해서 사용할 수 있다.

29. nextLevelnull인 경우에 강제로 upgradeLevel을 호출해서 해당 익셉션이 떨어지면 테스트 성공

 

 

 

37. if문장이 많이 들어 있던 이전 코드보다 간결하고 작업 내용이 명확하게 드러나는 코드가 됐다.

* 객체 지향 프로그래밍의 가장 기본 원리

> 각 오브젝트와 메소드가 각각 자기 몫의 책임을 맡아 일하는 구조로 만들어야한다.

> Service, Level, User 내부 정보를 다루는 자신의 책임에 충실한 기능을 갖는다.

> 오브젝트에게 데이터를 요구하지 말고, 정보를 가지고 있는 오브젝트에게 작업을 요청하라

> UserService User 에게 레벨 업그레이드 작업을 해달라

> UserLevel에게 다음 레벨이 무엇인지 알려달라요청하는 방식으로 동작하는 것이 바람직함.

> 만약 BRONZE 레벨을 BASICSILVER 사이에 추가하고 그 다음 레벨로 업그레이드하는 조건은 로그인 횟수 80번이라는 요청이 왔을 때, 각 요청사항은 어디를 손보면 되는지 쉽게 알 수 있을 것이다. 변경 후에도 코드는 여전히 깔끔하고 코드를 이해하는데도 어려움이 없을 것이다.

 

Boolean 인자 값은 다음 레벨로 업그레이드 될 것인가 아닌가를 지정. 이전처럼 다음 레벨을 직접 명시하는 것보다는 true, false를 통해 업그레이드를 확인하려는 테스트케이스인지 쉽게 알아볼 수 있다.

 

 

nextLevel을 통해 다음레벨이 무엇인지 레벨에게 물어본다.

65~69. 이 테스트 코드만 봐서는 업그레이드된 경우를 테스트하려는 것인지 쉽게 파악하기 어렵다.

 

UserServiceUserServiceTest의 코드값이 중복이 발생한다.

이런 숫자도 중복을 제거해야할까??

- 한가지 변경 이유가 발생했을 때 여러군데를 고치게 만든다면 중복이므로 제거해주는게 좋다.

 

 

레벨이 할일은 레벨에게 위임한다 > 객체지향 기본원칙

레벨 이늄생성시 현재레벨, 다음 레벨을 세팅하게 하여 그에 맞는 값 리턴하도록 설계

 

 

레벨 객체를 이용해 다음 레벨을 user객체에 세팅하는 부분은 User객체가 갖는다. 이런식으로 나누어 가짐으로써 소스가 간결해지고 각자의 역할이 있기 때문에 유지보수시 해당 역할만 수정하면 되므로 유지보수가 간편해진다.

 

 

JDBC외의 Hibernate, JDA, JDO DB커넥션 API를 사용할 수 있기에 인터페이스

 

 

 

 

11~12. 상수 숫자값을 한번만 변경될 수 있도록 하는 가장 좋은 방법은 위와같이 정수형 상수로 변경하는 것이다.

40. if else로 다음레벨을 셋팅해주던 것을 User에게 위임

숫자로만 되어 있는 경우 코멘트나 설계문서를 참조하기 전에는 이해하기 힘들었던 부분이 이제는 무슨 의도로 어떤 값을 넣었는지 이해하기가 쉬워졌다.

1.     만약 연말 이벤트나 새로운 서비스 홍보기간 중에 레벨 업그레이드 정책을 다르게 적용할 필요가 있을 수도 있다. 이를 코드로 수정한다는 것은 상당히 번거롭고 위험한 방법이다. 이런 경우 사용자 업그레이드 정책을 UserService에서 분리하는 방법을 고려해 볼 수 있다.

> 분리된 업그레이드 정책을 담은 오브젝트를 DI를 통해 UserService에 주입한다.

 

 

30, 32. -1 : 테스트는 가능한 한 경계값을 사용하는 것이 좋다.