Programming/Study

구체 클래스 vs 추상 클래스 vs 인터페이스 - 고객 / 바버 추상화하기

JKROH 2024. 2. 27. 00:57
반응형

 이전 글에서 현재 사용 중인 회원 도메인을 고객과 바버의 두 개의 도메인으로 분리하기로 했다. 고객은 바버샵과 연관관계를 맺지 않아도 되지만, 리뷰에 답글을 달거나 바버샵과 관련한 기능들을 수행하기 위해서 바버는 바버샵과 연관을 맺어야하기 때문이다.

 

 한 가지 고민해 볼 점은 고객과 바버를 완전히 다른 도메인으로 분리하느냐, 엔티티 레벨에서만 분리하고 하나의 도메인(회원)에서 사용하느냐이다. 물론 둘을 완벽하게 분리해서 사용하는 것은 충분히 가능하다. 애초에 회원가입을 시도할 때, 해당 사용자의 직군을 기준으로 서버 쪽에 요청을 다르게 날리면 된다. 이후에는 클라이언트에 회원의 정보를 쏴줄 때마다 해당 사용자의 역할을 담아 보내고, 그에 맞게 클라이언트에서 다시 요청을 보내면 된다. 이전 글에서는 클라이언트에서 이 둘을 어떻게 구분해야하는가에 대해 고민했는데, 회원 가입 페이지까지는 서버에 요청이 없고 회원 가입을 시도할 때 요청이 오기 때문에 충분히 가능하다는 것이다.

 

 그럼 이 둘을 완벽하게 분리하면 모든 문제가 해결될까? 그것도 아닌 것 같은게, 둘을 완벽하게 분리하면 임피던스 불일치 문제가 발생할 것이라고 예상된다. 현재는 사용자 테이블을 하나만 사용하고 있다. 임피던스 불일치 문제 해결을 위해 이 둘을 서로 다른 테이블로 나누면 인가 기능에서 어려움이 발생한다. 두 개의 레포지토리를 뒤져서 일을 처리해야한다. 또한 연관관계 칼럼 하나 때문에 굳이 테이블을 두 개로 나누고 관리해야 할 필요성이 생긴다.

 

 테이블 관점에서 고민해보면, 고객과 바버는 사용자라는 테이블을 상속해서 사용하는게 적절하다고 판단된다. 이전 글에서도 언급했다싶이, Spring Security를 사용한 인가 처리를 위해서 사용자 테이블이라는 하나의 테이블을 이용하는게 더 좋다고 생각했기 때문이다. 또한 칼럼 하나를 위해 다른 여러 칼럼들이 중복되는 두 개의 테이블을 운용하는 것이 좋아보이지는 않는다. 물론 이는 DB 정규화 관점에서 바라보면 정보 분리가 되지 않은 형태이고, null을 허용한 칼럼이 존재한다는 문제가 있다. 하지만 매 조회 시마다 테이블을 join하거나 회원 테이블을 조회하고 다시 근무지 테이블을 조회하는 조회 로직이 추가되는 문제와 비교했을 때 칼럼 하나의 문제를 안고가는 편이 더 낫다고 생각된다.

 

 그럼 코드레벨에서 이 둘을 어떻게 분리할 것이냐는 문제가 남았다. 처음 생각은 이 두 클래스의 차이가 필드 하나(근무 중인 바버샵의 id)이고 기능적으로는 차이가 없기 때문에 사용자 행동을 규약하는 하나의 인터페이스로 추상화하면 되지 않을까 싶었다. 그런데 유즈케이스를 좀 더 깊이 생각해보니 바버의 근무지 등록 / 수정은 조금 다르게 규정해야할 것 같다.

 

 바버가 처음 회원 가입할 때 근무지를 등록하거나, 근무지를 수정하는 경우의 예상하는 시나리오는 다음과 같다.

  1. 근무 중인 샵 / 이직 한 샵의 정보를 서버에 보낸다.
  2. 서버에서는 해당 샵의 원장으로 등록된 회원의 정보에 수락 요청을 보낸다.
  3. 수락이 완료되면 1번의 요청을 완수한다.

 이 과정에서 2번의 프로세스 처리 때문에 문제가 발생했는데, 일반 고객회원의 가입이나 수정과 달리 원장의 인증이라는 추가적인 시나리오가 발생하기 때문이다. 해당 과정을 근무 중인 샵의 id가 바뀌었냐 말았냐의 시점으로 바라보고 고객의 정보 수정과 마찬가지인 하나의 기능으로 담아낼 것이냐, 아니면 아예 기능을 분리해서 새로운 기능으로 처리할 것이냐에 따라 고객과 바버의 기능 추상화가 가능하느냐 마냐가 바뀐다.

 

 물론 해당 인증 과정을 아예 별개의 시나리오로 빼버리는 것도 가능하다. 등록 / 수정의 과정에선 인증을 하지 않고, 해당 과정이 완료되면 이런 과정이 수행되었다고 샵측에 메일이나 알림을 보내는 것이다. 그 이후에 허위 사실이면 샵측에서 알아서 정리하게 말이다. 이렇게 기능을 구현하면, 위의 과정은 아주 간단하게 추상화할 수 있다. 그냥 회원 정보 수정에 바버라면 추가적으로 근무 중인 샵의 id만 더 담으면 된다.

 

 둘을 하나의 규약으로 추상화하지 않으면 API부터 모든 부분을 분리해야한다. 이렇게 분리하면 장단은 명확하다. 장점은 두 도메인이 확실하게 분리되어있기 때문에 유지보수 관점에서 더 좋다. 두 개념을 깔끔하게 분리해서 다룰 수 있고, 어느 한 쪽에 문제가 생기면 해당 부분만 수정할 수 있다. 단점 역시 두 도메인이 분리된다는 점에서 나오는데, 먼저 다른 도메인에서 강하게 의존하는 도메인이 두 개로 늘어난다. 회원이라는 큰 도메인에는 결국 다른 도메인에서 모두 의존해야 하기 때문이다. 또한 코드 중복이 많이 발생한다. 회원 가입, 정보 수정 등 회원과 관련한 기능은 모두 두 개의 도메인에 중복되어 발생한다.

 

 결과적으로 Member라는 큰 틀의 규약을 걸고 진행하며, 인증은 샵측에 맡기는 편을 선택하는 것이 낫다고 생각한다. 둘을 나눔으로써 얻는 이득보다, 둘을 하나의 개념으로 생각해서 진행하는 편의 이득이 더 크다고 판단된다. 코드 중복은 프로그램을 지나치게 비대하게 만들고, 유지보수하기 어렵게 만든다. 또한 바버와 고객의 개념을 명확하게 분리할만큼 둘의 기능적인 차이가 많지 않다.

 

 그럼 추상화를 어떻게 진행하는 것이 옳을까? Member라는 콘크리트 클래스를 만들고 상속시킨다? 아니면 추상 클래스를 만들고 상속시킨다? 그것도 아니면 인터페이스로 만들어서 이를 구현하는 형태로 사용한다?

 

 셋 중 하나를 선택하기 위해선, 고객과 바버과 왜 나뉘게 되었는지를 먼저 판단해야 할 것이다. 고객과 바버가 서로 다른 엔티티로서 존재하게 되는 이유는 둘을 통합하면 근무 중인 바버샵 id라는 불필요한 필드가 null을 허용하는 형태로 존재하는 경우가 발생하기 때문이다. 그외의 대부분의 것은 동일하다. 바버의 경우 근무 중인 바버샵을 수정한다는 기능이 추가될 수 있다는 점 정도가 다르다, 이는 다형성을 이용해 해결 가능한 부분이다. 결국 둘을 나누게 된 이유는 기능의 차이라기보다는 서로 다른 데이터 값을 담아야하기 때문이라는 점이 더 크다.

 

 그러면 다시 돌아와서, 구체 클래스와 추상 클래스 / 인터페이스의 차이는 뭘까. 구체 클래스는 그 자체로 역할을 보유하고, 추상 클래스 / 인터페이스는 '이런 역할을 해야해'라는 규약을 부여한다. 즉 Member라는 구체 클래스를 상속하는 형태로 설계를 진행하면, Member자체가 지닌 역할도 있어야 한다는 것이다. 그러나 우리는 그런 형태로 설계를 진행하지 않는다. Member는 Barber와 Customer가 회원이라는 틀로 묶인다는 것을 표현할 뿐이다. 따라서 콘크리트 클래스를 상속하는 형태는 썩 옳지 못해 보인다.

 

 그럼 추상 클래스와 인터페이스 중 무엇을 선택하는 것이 옳을까. 다시 한 번 둘을 분리하게 된 이유를 생각하면 기능의 차이보다는 이들이 가져야하는 필드의 차이가 더 크다. 여기서 추상 클래스와 인터페이스 중 어떤 것을 선택할지가 결정될 수 있다.

 

 추상 클래스는 서로 다른 두 개념을 정의하는 공통된 정보와 기능을 묶는 상위 개념을 생성해서 이를 상속해 하위 개념에서 상위 개념의 규약을 지키도록 만든다. 인터페이스는 서로 다른 두 개념의 공통된 기능을 묶는 상위 개념을 생성해서 이를 상속해 하위 개념에서 규약을 지키도록 만든다. 추상 클래스를 만들어 상속하도록 하는 형태가 두 개념에 더욱 강한 규약을 거는 것이다.

 

 더 강한 규약이 걸린다는 말은 다르게 말하면 변화에 유연하게 대처하기 어렵다는 말이 된다. 추상화를 진행하고 다형성을 활용하는 이유는 비슷한 개념의 여러 객체를 좀 더 다양한 형태로 사용하기 위해서인데, 상속을 하면 다형적으로 객체를 처리하는데 더욱 제약이 걸린다. 해당 객체를 정의하는 값까지 규약하기 때문이다. 그만큼 상속으로 추상화를 처리하기 위해서는 더욱 명확한 개념 정의와 사용의 근거가 마련되어야한다. 반면 인터페이스로 규약을 건다면, 행위에만 규약이 걸리기 때문에 해당 행위를 하는 객체들을 좀 더 유연하게 묶을 수 있다. 물론, 리스코프 치환 원칙에 의거하여 추상화를 진행해야 한다. 그렇기 때문에 근무지 수정이라는 기능을 별개로 처리하지 않은 것이다. 중복되는 필드는 MemberInfo정도의 데이터 클래스로 정의해 조립하면 될 것이다.

 

 고객과 바버를 하나의 개념으로 묶어 사용하려면, 당연히 둘에서 파생될 수 있는 대부분의 클래스에도 추상화가 적용되어야 한다. 이 과정을 진행하다 보면 또 지금의 생각이 잘못됐고, 수정해야겠다고 생각할 수도 있다. 일단은 현재까지의 고민과 판단을 근거로 프로젝트를 진행하며 상황을 좀 더 보는 편이 옳겠다.

반응형