10장: 람다를 이용한 DSL
DSL이란
Domain Specific Languages라는 뜻으로, 특정 도메인을 대상으로 만들어진 특수 프로그래밍 언어이다.
Maven, Ant 언어는 빌드 과정을 표현하는 DSL이다.
DSL을 개발할 때에는 코드의 의도가 명확히 드러나 프로그래머가 아닌 사람도 이해할 수 있어야 한다. 또한 가독성 있는 코드를 구현해야 한다.
장점
간결함
DSL의 API는 비즈니스 로직을 캡슐화하므로 중복이 줄어들고 코드가 간결해진다.
가독성
도메인 영역의 용어를 사용하므로 비 도메인 전문가도 코드를 쉽게 이해할 수 있다.
유지보수
잘 설계된 DSL로 구현한 코드는 쉽게 유지보수하고 바꿀수 있다. 유지보수는 비즈니스 관련 코드, 즉 가장 빈번히 바뀌는 애플리케이션 부분에 특히 중요하다.
높은 수준의 추상화
DSL은 도메인과 같은 추상화 수준에서 동작하므로 도메인의 문제와 직접적으로 관련되지 않은 세부 사항을 숨긴다.
집중
비즈니스 도메인의 규칙을 표현할 목적으로 설계된 언어이므로 프로그래머가 특정 코드에 집중할 수 있어 생산성이 높아진다.
관심사 분리
지정된 언어로 비즈니스 로직을 표한함으로 애플리케이션의 인프라 구조와 관련된 문제와 관련 없이 비즈니스 관련된 코드에 집중하기가 용이하다.
단점
DSL 설계의 어려움
간결하게 제한적인 언어에 도메인 지식을 담는 것이 쉬운 작업은 아니다.
개발 비용
코드에 DSL을 추가하는 작업은 초기 프로젝트에 많은 비용과 시간이 소모되는 작업이다. 또한 DSL 유지보수와 변경은 프로젝트에 부담을 주는 요소이다.
추가 우회 계층
DSL은 추가적인 계층으로 도메인 모델을 감싸며 이 때 계층을 최대한 작게 만들어야 성능 문제를 회피할 수 있다.
새로 배워야 하는 언어
요즘 추세는 하나의 프로젝트에도 여러가지 언어를 사용한다. DSL을 프로젝트에 추가하며 배워야 하는 언어가 늘어난다는 부담이 생긴다.
호스팅 언어 한계
자바 같은 언어는 장황하고 엄격한 문법을 가졌다. 따라서 사용자 친화적인 DSL을 만들기가 힘들다. Java 8의 람다식은 이 문제를 해결할 수 있다.
내부 DSL, 외부 DSL
내부 DSL
임베디드 DSL이라고 불리며 순수 자바 코드같은 기존 호스팅 언어를 기반으로 구현한다.
기존 언어를 사용하기 때문에 새로 학습해야 할 내용이 줄어든다.
다른 언어의 컴파일러를 이용하거나 외부 DSL 도구가 필요하지 않아 비용이 절감된다.
언어가 같으므로 기존 IDE의 자동 완성, 리팩토링 등의 기능을 사용할 수 있다.
한개의 언어로 하나 또는 여러 도메인을 대응하지 못해 추가 DSL을 개발해야 하는 상황에서 자바를 이용하면, 추가 DSL을 쉽게 합칠수 있다.
외부 DSL
standalone이라 불리는 외부 DSL은 호스팅 언어와 독립적으로 자체적인 문법을 가진다.
새 언어를 파싱하고, 파서의 결과를 분석하고, 외부 DSL을 실행할 코드를 만들어야 하므로 복잡하다.
무한한 유연성을 가지므로 필요한 특성을 완벽하게 제공하는 언어를 설계할 수 있는 장점이 있다.
다중 DSL
자바가 아닌 언어지만 JVM에서 실행되는 Scala, Groovy 등의 언어가 이에 해당한다.
새로운 프로그래밍 언어를 배우거나 또는 팀의 누군가가 리딩 할 수 있어야 한다.
두 개 이상의 언어가 혼재하므로 여러 컴파일러로 소스를 빌드 하도록 빌드 과정의 개선이 필요하다.
JVM에서 실행되는 거의 모든 언어가 자바와 호환된다고 하지만 완벽히 호환되지 않는 경우가 존재한다.
자바 내부의 DSL
정렬
기존에 람다가 없었을 때에는 객체 컬렉션을 정렬할 때 아래와 같이 내부 클래스를 구현해야 했다.
이를 람다를 사용하면 아래와 같이 간결해진다.
메서드 참조와 Comparator.comparing 메서드를 static import 하면 더욱 간결해진다.
스트림 API
컬렉션의 항목을 필터, 정렬, 그룹화, 조작하는 작은 내부 DSL이다.
파일을 읽어 문자열 스트림을 만들고, ERROR로 시작하는 라인만 40줄 추출하는 요구사항을 간결하게 아래와 같이 작성할 수 있다.
Collectors
데이터 수집을 수행하는 DSL이다.
다중 수준의 그룹화를 아래와 같이 안쪽 그룹화부터 바깥쪽 그룹화까지 이뤄지도록 한다.
자바로 만드는 DSL 패턴과 기법
메서드 체인
자바에서는 아래와 같이 한 개의 메서드 호출 체인으로 주식 거래 주문을 정의할 수 있다.
메서드 체인 방식을 구현하기 위해서는 buy, sell 메서드의 반환 객체가 다음 메서드에서 어떤 주식을 구매할 지 선택하는 메서드가 존재하는 빌더 객체여야 한다.
마찬가지로 stock 메서드의 반환 객체는 어느 주식의 시장에 속하는 주식인지 선택하는 메서드가 존재하는 빌더 객체가 필요하다.
중첩된 함수 이용
메서드 체인에 비해 함수의 중첩 방식이 도메인 객체 계층 구조에 그대로 반영되는 장점이 있다.
결과 DSL에 더 많은 괄호를 사용해야 하고 인수 목록을 정적 메서드에 넘겨주어야 하는 단점이 있다.
중첩된 함수 방식을 구현하기 위해서는 하나의 클래스에 여러 정적 메서드들을 두고 order 메서드는 여러 Trade 객체를 입력받을 수 있도록 하고, buy 메서드는 구매 수량, 주식 정보, 가격 등을 입력받도록 해야 한다.
다시 stock 메서드는 주식 이름과 시장 이름을 입력받아야 한다.
람다 표현식을 이용한 함수 시퀀싱
메서드 체인 패턴처럼 플루언트 방식으로 정의할 수 있고, 중첩 함수 형식처럼 도메인 객체의 계층 구조를 유지한다.
자바 8 람다 표현식에 따른 잡음의 영향을 받는다.
람다 표현식을 받아 도메인 모델을 만들어내는 여러 빌더를 구현해야 한다.
아래는 order, buy, sell 메서드에서 Consumer 타입의 함수형 인터페이스 구현체를 입력받아 빌더 객체 내부 변수 자체를 변경하도록 하여 Order 객체를 생성해낸다.
조합하기
앞서 다룬 각 방식들을 조합하여 사용할 수도 있다.
DSL 개발의 장단점
Last updated