Interface 언제 만들까?

hyunji·2023년 6월 6일
post-thumbnail

글을 들어가며..

어떤 상황에서 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)=110 / (0 + 10) =1
      • 최고로 불안정
    • Service
      • Repository 의존 Service 10개, Service에 의존 Controller 20개
      • Service 불안정성 = 10/(20+10)=0.33310/(20+10)=0.333
      • 약간 안정적
    • Repository
      • Reoository에 의존하는 Service & Controller 20개
      • Repository 불안정성 = 0/(20+0)=00/(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거리 = |추상성 + 불안정성 - 1| 로 계산가능하다

    → 거리가 0이면 주계열 바로 위에 있고, 1은 멀리 떨어져 존재한다

    → 이 공식을 통해 패키지들이 잘 설계되었는지 조사해보자!

Controller, Service, Repository 들을 통해 살펴보자

  • Controller의 주계열로부터의 거리
    Controller의추상성0+Controller의불안정성11=0|Controller의 추상성 0 + |Controller의 불안정성 1 - 1| = 0
    → 주계열 바로 위이므로 GOOD!
  • Repository의 주계열로부터의 거리

    Repository의추상성1+Repository의불안정성01=0|Repository의 추상성 1 + Repository의 불안정성 0 - 1| = 0

    → 주계열 바로 위이므로 GOOD!

  • 그렇다면 Service는…

    → 상황에 따라 그때 그때 주계열에 가까운지 확인해봐야 한다.

1. EmailService의 상황

  • email 발송 시 시작은 SMTP 서버에 의존하겠지만, 나중에는 DB에 메일 내용을 넣고 배치작업으로 메일을 보내거나 아니면 MQ를 통해 메일 내용을 전달하고 다른 프로그램에서 실제 메일 발송을 하게 하는 경우도 있는등 변동성이 매우 크다.

  • 불안정성부터 계산해보면

    SMTP 의존 1 / 100여개의 이메일 호출 + SMTP 의존 1
    = 1 / (100 + 1)
    = 0.009

    → 매우 안정적

  • 만일 매우 안정적인데 추상성이 0이면

    주계열로부터의 거리가 0+0.0091=0.991|0 + 0.009 -1| = 0.991 이 된다.

    1이 가까우므로 문제가 있음을 알 수 있다 따라서 이 Service는 추상성을 1로 높이기 위해 Interface를 구현하는게 좋다!

    인터페이스로 구현하게 되면 주계열로부터의 거리가 0+0.0091=0.009|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)

0개의 댓글