티스토리 툴바

몇달전 Eric Evans 와 함께했던 워크샵에서 그는  인터페이스 스타일에 관해 이야기를 꺼냈고, 우리는 그러한 스타일의 인터페이스를 fluent interface 라 부르기로 했다. 비록 일반적인 스타일로 보기는 힘들지만 좀 더 알릴 필요가 있다고 생각된다. 이러한 인터페이스를 설명하는데 가장 좋은 방법은 예를 드는 것이다.

Eric 의 제시한 가장 단순한 예제는  timeAndMoney 라이브러리다. 일반적으로 시간 간격을 만들어내기 위해서 다음과 같은 코드를 흔히 볼 수 있다:
TimePoint fiveOClock, sixOClock;
...

TimeInterval meetingTime = new TimeInterval(fiveOClock, sixOClock);
timeAndMoney 라이브러리 사용자의 경우는 좀 다른 방법으로 이를 수행할 것이다:
TimeInterval meetingTime = fiveOClock.until(sixOClock);

다른 예로 특정 고객의 주문을 생성하는 것을 살펴보겠다. 주문은 다수의 상품 품목(line-items), 구매 제품과 수량 등을 포함한다. 특정 품목은 SKIP 이 가능한데, 이는 그 품목으로 인해서 배송이 지연되는 것을 막기 위해 해당 품목을 빼고도 전체 주문에 대해서는 배송이 이루어질 수 있는 것을 뜻한다. 또한, 주문 처리가 빨리 이뤄지도록 Rush 상태로 설정할 수 있다.
일반적으로 이를 위해서 다음과 같은 코드를 작성할 수 있다:
  private void makeNormal(Customer customer) {
       Order o1 = new Order();
       customer.addOrder(o1);
       OrderLine line1 = new OrderLine(6, Product.find("TAL"));
       o1.addLine(line1);
       OrderLine line2 = new OrderLine(5, Product.find("HPK"));
       o1.addLine(line2);
       OrderLine line3 = new OrderLine(3, Product.find("LGV"));
       o1.addLine(line3);
       line2.setSkippable(true);
       o1.setRush(true);
  }

요점만 설명하면 다수의 객체를 생성하고 이들을 엮어냈다. 만약 생성자에서 모든 설정을 할 수 없다면, 임시 변수를 만든다거나 컬렉션(Collection) 객체에 항목을 담는 것과 같은 일이 요구되었을 것이다.
동일한 일은 Fluent Interface 스타일을 적용해서 처리해보자:
  private void makeFluent(Customer customer) {
       customer.newOrder()
               .with(6, "TAL")
               .with(5, "HPK").skippable()
               .with(3, "LGV")
               .priorityRush();
  }

아마 가장 눈에 띄는 점은 내부의 DomainSpecificLanguage 가 수행하는 일들일 것이다. Fluent 라는 용어를 선택한 이유도 여기에 있다. 이러한 유형의 API 는 주로 가독성과 유창한 표현력에 초점을 둔다. 유창한 표현력을 위해서는 많은 사고가 요구되고 API 를 구축하는데 또한 많은 노력이 요구된다. 전자의 예처럼 생성자와 setter 그리고 객체를 추가하는 성격의 메소드와 같은 단순한 API 를 만들어내는 것은 쉽다. 반면에 훌륭한 Fluent API 를 고안하는 것은 상당한 생각을 요구한다.

만약에 Fluent API 의 예를 통해 더 많은 생각을 하고자 한다면, JMock 을 보라. JMock 역시 다른 Mocking(혹은 Mockup) 라이브러리처럼 복잡한 작동이 가능한 명세가 요구된다. 지난 수년간 많은 Mocking 라이브러리가 있었지만 JMock 은 매우 훌륭한 Fluent API 로 눈에 띈다. 결과값에 대한 기대치를 표현하는 예를 보자:
mock.expects(once()).method("m").with( or(stringContains("hello"),
                                         stringContains("howdy")) );

나는 JAOO2005 에서 Steve FreemanNat Price 가 발표한 JMock API 의 진화 과정에 대한 훌륭한 발표를 보았고, 그들은 자신들의 경험을 기록으로 정리하겠다고 했지만 이뤄지지 않았다. 제발 누군가 그들이 글을 집필할 시간을 갖게끔 그들의 컴파일러를 좀 쉬게 해주면 좋겠다. :)
지금까지 주로 객체의 환경 설정 과정에서 Fluent API 를 이용하는 사례들을 살펴 보았다. 이런 사례들로 Fluent API 를 특징지을 수 있을지는 의문이지만, 선언적 성격을 띈 상황에서 이들이 주로 쓰여지는데는 무언가 이유가 있을 법하다. Fluent API 를 검증하는 유용한 방법은 Domain Specific Language 의 품질이다.

Fluent API 는 보편적인 방식과는 조금 다른 형태로 API 를 사용하도록 유도한다. 그 중에서도 두드러진 것은 반환값을 갖는 setter 메소드이다. (위의 Order 예를 보면,  with 메소드로 order 객체에 order line 객체를 추가하는 경우 order line 객체가 반환된다.) 중괄호를 이용하는 대부분의 언어에서는 대개 수정을 가하는 메소드는 void 이다. 이러한 관습이 명령과 질의 분리(command query separation) 원칙을 따르기 때문 나는 이를 좋아한다. fluent interface 입장에서는 이러한 관습은 적절하지 않기 때문에 이 경우에 대해서는 그러한 관습을 적용하는 것은 적합하지 않다.

연속적으로 원활한 작업(fluent action)을 하기 위해서 return type 을 결정할 수 있다. JMock 은 다음에 필요한 작업이 무엇이냐 근거해서 return type 을 결정하는 중대한 시사점을 제시했다. 이러한 스타일의 두드러진 장점은 IDE 의 마법사 따위를 쓰지 않고 메소드 자동 완성 기능(intellisense)을 통해서 다음에 수행할 코드를 쉽게 도울 수 있다는 점이다. 일반적으로 동적인 언어(Ruby, Python 등)는 간결한 문법을 사용하기 때문에 DSL 용도로 적절하다는 것을 발견했다. 그러나, 메소드 자동 완성 기능을 이용하면 정적인 언어(Java, C 등)에게도 상당한 강점이 생긴다.

fluent interface 를 갖는 메소드가 갖는 문제를 지적하면, 메소드 자체만 놓고 보면 의미가 분명하지 않을 수 있다는 점이다. with 메소드를 API 문서 등을 통해서 순차로 나열된 메소드 가운데서 찾아 보게 된다면 정확한 의미를 파악하기가 쉽지 않다. 그 자체로만 보면 의도를 알 수 없는 모호한 이름을 가진 것이 된다. fluent interface 는 연속적으로 이어지는 행위(fluent action) 속에서 쓰여질 때 강점을 갖는다. 이러한 경우에 그대로 부합하는 예로 빌더 객체(builder objects)를 들 수 있다.
Eric 의 경험에 따르면, fluent interfaces 는 주로 Value objects 의 속성 값을 설정해주는 역할에 주로 쓰인다고 한다. Value objects 는 업무적 관점에서 식별자(domain-meaningful identity)를 갖지 않기에(ID 가 없으면, 단순히 값을 실어 나르는 역할을 함)  쉽게 이들을 생성하거나 제거할 수 있다. Evans의 도메인 객체 분류(Evans Classification)에 따르면 Order 예에서 Order 객체는 Entity 에 해당하기 때문에 전형적인 예라고 볼 수는 없다.

나는 아직 fluent interfaces 의 다양한 예를 보지 못했기 때문에 우리는 그 장단점을 충분히 알지 못한다고 결론을 내리겠다. 그래서, 이들을 사용하라는 어떠한 권고도 이른 감이 있지만, 아마 fluent interfaces 에 대해 다양한 시도를 해볼만한 시점이라고 생각된다.
원문: FluentInterface

답글 보기


설정

트랙백

댓글