글을 들어가며..
어떤 상황에서 Interface를 만들어야 할까? 개발 하는 사람들에게 많은 고민을 안겨주는 주제이다. 해당 이슈를 생각해보기 위해 제이슨이 보내주신 우아한 기술 블로그의 “안정된 의존관계 원칙과 안정된 추상화 원칙에 대하여” 에 대한 글을 읽고 해당 이슈에 대해 정리해보았다…
안정된 의존 관계 원칙 (Stable Dependencies Principle, SDP)
의존은 안정적인 쪽을 향해!
- 소프트웨어 설계는 정적이지 않고, 유지보수 때문에 변화가 필수다.
- 쉽게 바뀔 거라고 예상되는 패키지들이 바뀌기 어려운 패키지의 의존 대상이 되어선 안된다. 왜냐하면 쉽게 바뀔 수 있는 모듈에 다른 것들이 의존하기 시작하면 이 모듈은 변경하기 어렵게 되기 때문이다.
안정성이란?
- 다른 많은 소프트웨어 패키지가 ‘A’ 패키지에 의존하게 만들면, ‘A’ 패키지는 변경이 힘들어 진다. 이 ‘A’ 패키지가 변경이 될 때 이 변경한 내용이 의존하는 모두를 만족시키려면 많은 일이 필요하므로 매우 안정적이다.
패키지의 안정성 측정
-
안정성은 패키지에 의존하는 수와 패키지가 의존하는 수를 통해 측정 가능하다
-
불안정성 계산 식

-
불안정성 == 1
- 이 패키지에 의존하는 다른 패키지 X
- 의존적 & 불안정
-
불안정성 == 0
- 이 패키지에 의존하는 다른 요소가 많아 함부로 변경이 어려우며, 다른 것에 의존하지 않아 의존성에 의해 변경될 가능성 적음
- 책임 & 독립적 & 안정적
-
안정된 의존관계 원칙에 따르면…..
- 어떤 패키지의 불안정성 값 > 그 패키지가 의존하는 다른 패키지들의 불안정성 값
- 즉, 의존 관계의 방향으로 불안정성 측정값이 줄어들어야 한다!!
Controller → Service → Repository의 안정성
- Controller는 일반적으로 Service & Repository에 의존한다. 하지만 Controller에 의존하는 다른 코드는 존재하지 않는다.
- example
- Controller
- 10개 모두 Service & Repository에 의존 O + 어느 것도 Controller에 의존 X
- Controller 불안정성 = 10/(0+10)=1
- 최고로 불안정
- Service
- Repository 의존 Service 10개, Service에 의존 Controller 20개
- Service 불안정성 = 10/(20+10)=0.333
- 약간 안정적
- Repository
- Reoository에 의존하는 Service & Controller 20개
- Repository 불안정성 = 0/(20+0)=0
- 최고로 안정적
- 의존 관계 뱡항으로 불안정성 값이 줄어듦을 확인할 수 있다

- 안정된 의존 관계 원칙으로 보면,
- Repository는 절대로 Service나 Controller에 의존하면 안되고,
- Service도 Controller에 있는 코드를 호출해선 안된다
- 안정적인 설계가 필요한 코드는 유연성이 떨어진다. 그렇다면 안정적이면서 설계의 유연성을 높일 수 있는 방법이 있을까?!?!?!?!?
- 바로 추상 클래스(abstract class)이다!
안정된 추상화 원칙(Stable Abstraction Principle)
안정적인만큼 추상적으로!
- 패키지는 자신의 안정적인 만큼 추상적이여야한다.
- 안정적인 패키지는 안정성 때문에 확장이 불가능하지 않도록 추상적이여야 한다. 반대로 불안정한 패키지는 구체적이어야 하는데, 패키지 안의 구체적인 코드가 쉽게 변경될 수 있어야 하기 때문이다.
- 따라서 패키지가 안정적이라면 확장 가능하도록 추상 클래스들로 구성되어야 하며, 확장 가능한 안정적인 패키지는 설계를 지나치게 제약하지 않고 유연해야 한다.
- 안정된 의존 관계 원칙은 의존관계의 방향이 안정성의 증가 방향과 같아야 한다고 말하고, 안정된 추상화 원칙은 안정성이란 추상성을 내포한다고 말하기 때문에, 결론적으로 의존 관계는 추상성의 방향으로 흘러야 한다!!
따라서 위에 본 예시를 활용해보자면…
- Controller는 불안정하므로 추상적일 필요가 없다 → Controller 클래스 Interface를 만들고 구현하는 것은 무의미
- Repository는 매우 안정적이므로 추상적이어야 한다 → Repository는 Interface를 만들고 구현하는 것이 좋다.
- 그렇다면 Service는 만들어? 안만들어…?🤯
패키지 추상성 측정
- 추상성

- 추상성이 '0' == 패키지에 추상클래스 존재X
- 추상성이 '1' == 패키지에 추상 클래스 밖에 없다
안정성과 추상성의 관계

"[출처] 우아한기술블로그 “안정된 의존관계 원칙과 안정된 추상화 원칙에 대하여”"
- 추상성과 안정성의 관계를 그려보면 위의 그래프와 같다.
- 물론 모든 클래스가 (0,1) 이나 (1,0)에 올 수는 없다.
- 가장 좋은 경우는 저 주계열 부분에 클래스가 위치할 수 있도록 설계하는 것!!
- 쓸모없는 지역과 고통의 지역은 배제하길… 왜 배제해야할까?
고통의 지역
- 고통의 지역은 (0,0) 안정적이고 구체적인 패키지가 존재하는 경우이다.
- 이때 이 지역에선 추상적이지 않고 구체적이므로 확장이 어렵고, 안정적이라 변경도 어렵다.
- 하지만 여기 있을 수 밖에 없는 예외가 존재..
- 데이터베이스 스키마
- String 관련 클래스 (변경 가능성이 매우 적어 괜찮!)
쓸모 없는 지역
- 쓸모 없는 지역은 (1,1) 불안정하고 추상적이다.
- 불안정하므로 이에 의존하는 다른 코드가 없고 추상적이므로 인터페이스를 만들고 구현했다고 생각하면 된다.
- Controller를 만들 때 Interface를 만들고 구현하면 이 쓸모 없는 지역에 들어가게 된다. 따라서 이것만 봐도 쓸모 없는 행위라는 것을 알 수 있다!
주계열
-
주계열에 위치한 패키지들은 안정성에 비해 너무 추상적이지 않고, 추상성에 비해 너무 불안정적이지도 않는다. 따라서 자신이 추상적인 정도만큼 의존의 대상이되고, 자신이 구체적인 정도만큼 다른 패키지에 의존다.
-
주계열로 부터의 거리
거리=∣추상성+불안정성−1∣ 로 계산가능하다
→ 거리가 0이면 주계열 바로 위에 있고, 1은 멀리 떨어져 존재한다
→ 이 공식을 통해 패키지들이 잘 설계되었는지 조사해보자!
Controller, Service, Repository 들을 통해 살펴보자
- Controller의 주계열로부터의 거리
∣Controller의추상성0+∣Controller의불안정성1−1∣=0 → 주계열 바로 위이므로 GOOD!
1. EmailService의 상황
-
email 발송 시 시작은 SMTP 서버에 의존하겠지만, 나중에는 DB에 메일 내용을 넣고 배치작업으로 메일을 보내거나 아니면 MQ를 통해 메일 내용을 전달하고 다른 프로그램에서 실제 메일 발송을 하게 하는 경우도 있는등 변동성이 매우 크다.
-
불안정성부터 계산해보면
SMTP 의존 1 / 100여개의 이메일 호출 + SMTP 의존 1
= 1 / (100 + 1)
= 0.009
→ 매우 안정적
-
만일 매우 안정적인데 추상성이 0이면
주계열로부터의 거리가 ∣0+0.009−1∣=0.991 이 된다.
1이 가까우므로 문제가 있음을 알 수 있다 따라서 이 Service는 추상성을 1로 높이기 위해 Interface를 구현하는게 좋다!
인터페이스로 구현하게 되면 주계열로부터의 거리가 ∣0+0.009−1∣=0.009 가 되므로 주계열과 아주 가까워 아주 좋다!
2. 비즈니스 Service의 상황
-
Repository 5개를 호출해 그 결과를 조합해 다른 결과를 도출하고 Controller 한 개에서만 사용되는 Service가 있다고 가정해보자.
-
불안정성을 계산해보면
Repository 의존 5 / 1개의 컨트롤러가 호출 + Repository 의존 5
= 5 / (1 + 5)
= 0.83
→ 매우 불안정적
-
이 불안정한 클래스를 추상적으로 만드는 것은 의미가 없다.
-
하지만 시간이 지남에 따라 안정성이 변할 수도 있고 어떤 패키지 군에 속하느냐에 따라 추상적이여야 할 수도, 구체적이어야 할 수도 있다.
그래서 Service에 interface를 만들어 말아?
- 결론적으로 Service에 대해 Interface를 ,만들지 말지에 두가지 케이스로 구분해 볼 수 있다
인프라성 서비스와 Repository
- Email 발송, Push, SMS 발송, Logging 등등 인프라성 코드는 거의 불안정성이 0에 가까운 안정적인 클래스이다.
- 안정성이 높으면 변경 대응을 위해 추성적이여야한다. 따라서 인프라성 Service는 거의 무조건 interface를 구현해야 하며, Interface의 메소드 시그니쳐 구현에 구현의 상세를 포함해선 안된다.
- Repository도 마찬가지로 interface를 구현해야 한다. 지속적으로 성장하는 서비스를 맡아 DB가 여러번 변경되는 상황이 생길 수 있기 때문에 안정성이 높고 잦은 변경도 수용할 수 있도록 추상적으로 만들길 권장한다.
비즈니스 서비스
- 상황에 따라 다르기 때문에 상황을 잘 지켜봐야한다.
- 무지성으로 Interface를 만들면 쓸모없는 지역에 떨어질 수 있으니….해당 비즈니스의 성장에 따라서 위와 같이 주계열로부터의 거리를 계산해보고 맞는 상황일 때에는 Interface로 분리해보자.
글을 마무리 하며…
언제 Interface를 만들어야 하는지 감이 안왔었는데,
해당 기술 블로그를 읽으며 “안정성 & 추상성 & 주계열로부터의 거리” 처럼 객관적인 수치화 된 데이터로 살펴보니 어느정도 궁금증이 해소된 것 같다. 앞으로 Interface를 만들지 말지 고민될 땐 저 위의 식들을 활용해보자.
다만 이런 계산의 과정을 Interface를 만들지 고민될 때마다 매번 해야하나? 라는 생각이 들었다. 근데 여쭤보니 개발자분들은 경험이 쌓이셔서 이런 계산과정을 거치지 않아도 딱 만들지 말지 눈에 보이는 경우가 많다고 하신다… 나도 언젠간 그렇게 보이길 바라며…
"Interface 언제 만들지?" 글을 마무리한다.
[참고 자료]
안정된 의존관계 원칙과 안정된 추상화 원칙에 대하여 | 우아한형제들 기술블로그
계층화 아키텍처 (Layered Architecture)