상속 대신 위임이라는 고전적인 이야기의 2019년 버전

낮에 동료가 칠판에 그린 그림과 설명하는 말을 듣고, 동료의 질문에 답을 하다가, 무려 1994년에 나온 디자인 패턴 내용을 말하게 된 사연이다.

상속 대신 위임

그가 그린 그림은 아래와 같다.
presentation
벤 다이어그램으로 포함관계를 그린 동료의 그림
나는 그에게 물었다.
Order가 Sale을 포함한다는 말은 무엇을 의미하냐?
그는 자기 인식과 프로그램으로 구현한 내용을 나름대로 명확하게 설명했다. 나는 구현에 대해 옳고 그름을 판단할 생각이 없어[1] 그가 포함관계로 정의한 부분에 대해서만 문제 제기를 했다. 그러는 과정에서 디자인 패턴에 나오는 말을 인용했다. 위키피디아를 보니 Composition over inheritance지만, 내가 편안하게 여기는 우리말 표현은 상속 대신 위임이다.

포함이 상속인가?

엄밀히 따지면 차이가 있겠으나 대충 그렇다. 1994년 디자인 패턴 책이 나올 무렵 OOP 언어들은 상속 메커니즘을 제공했고, 상속의 남용이 문제가 되었다. 상속은 부모 클래스가 구현하는 내용을 하위 클래스에서 전부 내려 받는다. 그래서 결과적으로는 클래스(유형) 측면에서는 부모 클래스가 자식 클래스를 포함하고, 피처를 집합으로 표현하면 도리어 역의 관계가 성립[2]한다. 아무튼 디자인 패턴 책 1장에 상속의 부작용을 줄이는 방법으로 인터페이스 활용과 객체 조합object composition을 활용하는 이른바 위임을 구현 방법을 설명했다.
나는 동료의 설명에서 불필요한 듯 보이는 포함관계를 보면서 시간을 거슬러 올라가 디자인 패턴 1장에서 강조한 내용을 떠올리고, 그에게 관련 설명을 시도했다. 

포함은 나쁜 것인가?

그렇다면, 포함관계가 잘못된 것인가? 포함(包含)의 한자 뜻은 '쌀 포'와 '머금을 함'의 조합이다. '싸서 머금다'는 뜻이다. 안에 둔다는 뜻인데, 안에 둬서 유용하냐 아니면 불편하냐 하는 실용적 관점으로 상황에 맞고 맞지 않고를 따지고자 한다. 아래 그림에서 U의 안쪽이면서 A의 바깥쪽인 부분을 A의 여집합이라고 부른다.
presentation
여집합 표현 (출처: 구글 이미지 검색)
동료가 그린 그림에서 Order이면서 Sale의 여집합인 부분은 무엇일까? 나는 포함관계 활용이 적절한지 판단은 그 부분이 담고 있는 내용물에 달려 있다고 판단했다. 내 질문에 그는 이렇게 답했다.
상태값이라고 생각합니다.

모노리식 개발 경험의 전수

나는 이렇게 말했다.
판매 결과를 Sale 이라고 하고, 판매가 갖고 있지 않은 상태를 Order가 맡는다면 굳이 포함관계가 왜 필요하지?
그는 굳이 필요하지 않은 정도를 지적하는 내 태도를 보면서, 그렇다고 꼭 포함을 하지 말아야 하는지 의아해했다. 그러면서 자기가 구현한 논리를 설명하고 문제를 확인하고 싶어했다.
Order-API에서 받아온 데이터를 일단 org-order 테이블에 저장하고 <중략> 이런 방식에 문제가 있나요?
org-라는 접두어는 선배에게 배운 원본 데이터를 구분하는 방법이라고 했다. 결국 포함의 폐해는 Sale이 Order와 교집합일 수 있다는 전제로 모호하게 정의한 인식에 있다. 상속에 상위 클래스의 피처가 필요한지 그렇지 않은지에 상관없이 모두 내려 받는 것을 일종의 설계 취약점으로 볼 수 있다. 그래서 디자인 패턴 책에서는 대안으로 위임 혹은 객체 조합을 대안으로 말했다. 또 다른 책에서 소개한 SOLID 원칙 중 하나인  OCPOpen Closed Principle 역시 상속을 적절하게 쓰는 방법을 설명한다. 이를 인식의 경우로 치환하면 꼭 교집합 이유가 없는 것을 교집합이라고 가정하는 일이다.
우리 사례로 돌아가서 이야기하면, 원본 데이터를 받아오는 것이 공통 혹은 교집합이라고 전제하는 일은 설계 취약점일 수 있다. 데이터 복제나 EAI를 연상시키는 방법이 설계 취약점으로 보인다. 왜냐하면, 원본 데이터를 가져오는 시점에 서로 합의한 즉, 두 프로그램 사이에서 주고 받을 수 있는 형태의 데이터면 된다. 사소해보이지만, 굳이 다른 객체(우리 예에서는 Order)의 데이터 구조 모양을 그대로 따르는 일은 불필요한 개입이자 결합으로 수정이 어렵게 하는 일을 유발할 수 있다. 이러한 일은 설계 노하우를 아직 배우지 못한 동료가 다른 선배 동료에게 노하우를 배우는 과정에서 그들의 과거 경험이 묻어온 것으로 전수과정에서 흔히 벌어지는 일이다.

BoundedContext 소환하기

이쯤에서 과거에 써둔 글을 재사용하자. BoundedContext를 다룬 과거 글에서 필자가 부제로 이런 글을 쓴 일이 있다.
같은 말도 맥락에 따라 다르게 쓰인다
같은 주문 데이터라도 맥락에 따라 다르게 쓰인다. Sale을 Order에 포함시키지 말고, 구분하여 독립적인 객체[3]로 만든다면 Sale과 Order는 맥락이 달라진다. 그렇다면 애초에 Order 객체 혹은 Order-API에 요청하여 받아온 데이터는 원본으로 받아두지만 Sale에서 임의로 저장하는 데이터와 설사 필드가 겹칠 수 있지만 전혀 다른 맥락을 유지할 수 있다. 이러한 과정에서 필연적으로 약간의 중복은 발생하겠지만, 그러한 중복은 자유로운 맥락 구현을 위한 메커니즘이다. 물론, 그 중복조차 줄이겠다고 마음 먹는다면 Chris Richardson 등이 제안하는 이벤트 기반의 구현 방법이 효과를 발휘할 것이다.

결론

우리의 경우는 데이터 모양이 개발을 착수하는 시점에 이미 존재하는 다른 객체의 구조를 따라가면서 자연스럽게 포함관계가 그려졌다고 생각한다. 하지만, 그것은 주문이 먼저 개발된 우리 현장의 상황논리 혹은 우연에 의한 것이다. 그 우연을 강화할 필요는 없다[4]. 도리어 그러한 불필요한 특수성을 제거해야 유연해지고, 그에 따라 비즈니스 구현의 속도를 보장할 수 있다. 처음의 그림에서 Order와 Sale을 두 가지 구분할 수 있는 객체로 본다면 각자의 임무만 갖고 수평적으로 일정부분 다른 객체에게 위임하는 편이 책임을 구분하기 좋고, 같은 이치로 코드를 유지하기 좋다는 말이다. 그리고 공통으로 묘사한 부분은 현재는 Order에서 Sale로 가는 약속된 데이터인데, 그것도 각자 구현 방식을 간섭하지 않기 위해서는 API에 의한 호출과 메시지 전달 방식으로 의존성을 약하게 만들 필요가 있다.
presentation

메시지 전달 방식을 묘사한 그림 (출처: 구글 이미지 검색)
커머스 혹은 유통 도메인 설계에 대한 연작 (지난 글)
1편. 커머스 혹은 유통 도메인 설계에 대한 연작
2편. 상품 정보 관리 라이브사이클 정의
3편. 가격 할인 기능을 서비스로 바꿔보기
4편. 상품 정보 다룰 때 BoundedContext 와 엔터티
5편. 애그리게잇 하나에 리파지토리 하나
6편. DDD의 BoundedContext가 우리의 이야기와 밀접해지다
7편. Go 개발자가 UML을 사용한 이유
8편. ID로 다른 애그리게잇을 참조하라
9편. Aggregate를 애그리게잇 대신 조립물로 쓴 사연
10편. DDD 값 객체와 마이크로서비스

주석

[1] 나는 개발자가 아니므로 비즈니스 요구를 벗어나는 구현에 대해서는 과거 경험으로 조언을 할 수는 있을지 몰라도 개입하지 않으려고 노력한다.
[2] 이런 문제로 프로그래밍 언어 기초를 가르칠 때 형 변환을 다뤄지기도 한다. 
[3] 여기서 객체는 OOP의 생성단위를 말하는 것이 아니라 독립적인 하나의 프로그래밍 단위를 말하는 것이며, 위 글에서 다루는 단위는 하나의 마이크로 서비스 혹은 모듈을 말한다.
[4] 우연을 강화하면, 그곳에서만 통하는 어떤 사연이 만들어지는데 Martin Fowler는 이를 Business illogic라 표현한 바 있다. '우린 원래 그래요.'와 같은 식의 대답을 들어본 사람은 어떤 것을 칭하는지 짐작이 갈 것이다.