domain specific languages with groovy
DESCRIPTION
Domain Specific Language를 그루비로 만드는 법TRANSCRIPT
Domain-Specific Lan-guages With Groovy
Chun-Kil [email protected], Twitter: @gilbirdk
Expressing Requirements...
해결책은 ? DSL !
• 표현력이 월등 more expressive
• 공통의 관심사 common metaphore
• 전문가 domain expert 의 도움• 비즈니스 로직과 애플리케이션 코드 분리• 순환적 라이프사이클
DSL 이란 ?
• DSL: Domain-Specific Language– “Little Language”
• Wikipedia– DSL 은 특정 작업의 사용 목적으로 설계한
프로그램 언어 .
• DSL 은 개념을 언어 구문으로 만들 수 있는 특정 범주의 지식에 한정된 언어이다 .
DSL ...
• 온 세상이 DSL–기술어 Technical Dialects
–표기법 Notations
–비즈니스 용어 Business Languages
^[\w-\.]+@([\w-]){2,4}$
DSL 의 특징• DSL 은 언어이다 .• 특정 지식의 범주를 다룬다 .• 형식을 가지고 있다 . ( 문자 or 그림 )• 결과를 산출한다 .– 객체 설정 , 데이터 구조 표현
• 내부 혹은 외부에 존재– 호스트 언어에 임베드 (E-DSL )– Standalone (custom parser)
• 어떤 질적 속성을 가짐– 가독성 readability– 가속성 writability – 유용성 usability– 검증성 testability– 확장성 extensibility
Why Groovy?!
HelloWorld.javaimport java.util.*;
public class HelloWorld{ public static void main( String[] args ) { List langs = new ArrayList();
langs.add( "Groovy" ); langs.add( "Grails" ); langs.add( "Griffon" );
Collections.sort(langs );
for( Iterator it = langs.iterator(); it.has-Next(); ) System.out.println( "Hello " + it.next() ); }}
HelloWorld.groovy
['Groovy', 'Grails', 'Griffon'].sort().each { println "Hello $it"}
읽기 쉬운 코드 ? (Java)
읽기 쉬운 코드 ? (Groovy)
왜 Groovy 인가 ?
• 왜 Groovy 인가 ?–내부 / 임베드 DSL 생성 가능–임베드 DSL 은 Java 애플리케이션에 손쉽게
내장가능– DSL 로 커버 못하는 영역은 Groovy 사용
• Custom parser 는 ?– lexer/parser 는 구현 , 유지보수 및 사용이 복잡–추가 구현 힘듬• 복잡한 구문 구현 어려움
실세계 Groovy 적용 예• 보험 정책 위험도 계산 엔진• 은행 계좌 규칙• 대출 승인 규칙• 인력 자원 : 직원 스킬 표현• 바이러스 백신 시뮬레이션• 시나리오에 따른 마켓 데이터 생성• ...
Groovy DSLs
MarkupBuilder
new groovy.xml.MarkupBuilder().invoices { invoice(id: "4") { line("product 1") line("product 2") }}
<invoices> <invoice> <line>product 1</line> <line>product 2</line> </invoice></invoices>
Groovy DSLs
• XMLBuilder• HTMLBuilder• AntBuilder• SwingBuilder• Grails• Griffon• Gradle• Grape
Groovy 가 제공하는 것들
Java vs. Groovy
• Java– Class 행동–컴파일러가 모든 규칙 인지–행동은 바이트코드로 고정
• Groovy– Class 와 MetaClass 행동• 동적 언어
–컴파일러는 행동에 대하여 모름–행동은 완전히 실행시간에 동적임
Groovy MOP
• Meta Object Protocol
• 모든 것은 MOP 을 통함–메소드 호출 , 속성 액세스 , 연산자 ...–그래서 Groovy 는 “동적 언어”
• 완전하게 실행시간에 Groovy 코드를 커스터마이즈 가능함– 1+1=1 ? 가능함 ! 어떻게 ?
런타임 시스템 후킹• GroovyObject
– invokeMethod()– get/setProperty()
• Categories
• MetaClass– invokeConstructor() / method() / staticMethod()– invokeMissingMethod() / invokeMissingProperty()– get/setProperty()
• ExpandoMetaClass– Integer.metaClass.plus = { Integer i ->
1 }
연산자 오버로딩• + a.plus(b)• - a.minus(b)• * a.multiply(b)• / a.divide(b)• % a.modulo(b)• | a.or(b)• & a.and(b)• a[b] a.getAt(b)• a<<b a.leftShift(b)
• 환율 연산• 30.won + 15.euro
• 거리• 12.kilo.meters +
3.meters
• 병렬 , 작업 흐름• taskA & taskB | taskC
• 계좌 기입• account << 10.won• account += 10.dollar
속성에 숫자 추가• category로 숫자에 메소드와 속성 추가 가능
• class MyCategory { static Distance getMeters(Integer n) { new Distance(n, Distance.METER) }}
• use (MyCategory) { println 3.meters}
ExpandoMetaClass
• Grails 에서 ExpandoMetaClass 공헌
• Integer.metaClass.getMeters = { -> new Distance(delegate, Dis-tance.METER)}
• println 3.meters
융통성 있는 문법• 괄호 생략
– move left– monstor.move x: 3.meters, y: 4.meters– compare indicator: ’NIKEI’, withFund: ’XYZ’– account.debit amount: 30.won, in: 3.days
• 리스트와 맵 문법 내장– List
• [1, 2, 3, 4]
– Map• [a:1, b:2, c:3]
– Range• Monday..Friday
트리 구조 빌더• 트리 구조 데이터 생성 가능• 클로저를 마지막 인자로 받는 메서드 체인 호출 구조
– new MarkupBuilder().invoices { invoice( id: “4”) { line “product 1” line “product 2” }}
• 손쉽게 자신만의 빌더 생성 가능 !
BuilderSupport
• Implement BuilderSupport
• 구현 해야 하는 메서드createNode(name)createNode(name, map)createNode(name, value)createNode(name, map, value)nodeCompleted(parent, node)postNodeCompletion(parent, node)
커스텀 제어 구조• 클로저 closure를 메서드 파라메터로 전달
– unless( account.balance<0, {account.debit 10.dollars})
• 단축 표기법– unless (account.balance < 0) { account.debit 10.dollars}
• 무엇이든 만들어 보세요 !– withLock(aLock) { ... }– transactional { ... }– async { ... }– execute(within: 50.seconds) { ... }
RobotBuilder
def wallE = new RobotBuilder().robot('Wall E') { forward( dist: 20) left( rotation: 90) forward( speed: 10, duration: 5)}
wallE.go()
Builder 최종 인스턴스 구조robot
forward
dist: 20
left
rotation: 90
forward
speed: 10, duration: 5
FactoryBuilderSupport
class RobotBuilder extends FactoryBuilderSupport {
{
registerFactory( 'robot', new
RobotFactory())
registerFactory( 'forward', new ForwardMove-
Factory())
registerFactory( 'left', new LeftTurnFac-
tory())
};
}
Robotclass Robot {
String name
def movements = []
void go() {
println "$name 로봇 동작합니다 ..."
movements.each { movement ->
println movement
}
}
}
ForwardMove, LeftTurn
class ForwardMove {
def dist
String toString() { " 이동 ! 거리 ... $dist" }
}
class LeftTurn {
def rotation
String toString() { " 좌회전 ! 이동각 ... $rotation
도 " }
}
AbstractFactorypublic abstract class AbstractFactory implements Factory {
public boolean isLeaf() { return false; } public boolean onHandleNodeAttributes( FactoryBuilderSupport builder, Object node, Map attributes) { return true; } public void onNodeCompleted( FactoryBuilderSupport builder, Object parent, Object node) { } public void setParent( FactoryBuilderSupport builder, Object parent, Object child) { } public void setChild( FactoryBuilderSupport builder, Object parent, Object child) { }}
RootFactory
class RobotFactory extends AbstractFactory {
def newInstance( FactoryBuilderSupport builder,
name, value,
Map attrs) {
new Robot(name: value)
}
void setChild( FactoryBuilderSupport builder,
Object parent,
Object child) {
parent.movements << child
}
}
LeftTurnFactory
class LeftTurnFactory extends AbstractFactory {
boolean isLeaf() { true }
def newInstance( FactoryBuilderSupport builder,
name,
value, Map attrs) {
new LeftTurn()
}
}
ForwardMoveFactory
class ForwardMoveFactory extends AbstractFactory {
boolean isLeaf() { true }
def newInstance( FactoryBuilderSupport builder, name,
value,
Map attrs) {
new ForwardMove()
}
...
ForwardMoveFactory
...
boolean onHandleNodeAttributes( FactoryBuilderSupport
builder,
Object node, Map attrs)
{
if( attrs.speed && attrs.duration) {
node.dist = attrs.speed + attrs.duration
attrs.remove( 'speed')
attrs.remove('duration')
}
true
}
}
RobotBuilder
Wall E 로봇 동작합니다 ...이동 ! 거리 ... 20좌회전 ! 거리 ... 90 도이동 ! 거리 ... 15
DSL 애플리케이션 통합
통합 매커니즘• Java 6 : JSR-223 / javax.script.*
• Groovy 자체 메커니즘– GroovyShell– GroovyClassLoader
• Spring 2.0 dynamic language beans– Lang namespace– POGO customizer
Java 6 스크립팅 API
• scripting.dev.java.net 에서 Groovy 엔진 JAR 제공– CLASSPATH 에 포함시킬 것 !
• ScriptEngineManager manager = new ScriptEngineMan-ager();
ScriptEngine gEngine = manager.getEngineByName(“groovy”);
String result = (String)gEngine.eval(“’Foo’*2”);
GroovyShell
• expression 과 스크립트를 evaluate
• 바인딩을 통하여 값 입 / 출력
• evaluate 한 스크립트는 global 함수나 변수를 포함한 베이스 클래스를 가짐
GroovyShell 예• def binding = new Binding()binding.mass = 22.3binding.velocity = 10.6
def shell = new GroovyShell(binding)def expr = “mass * velocity ** 2 / 2”
assert shell.evaluate(expr) == 1252.814
GroovyClassLoader 예• GroovyClassLoader gcl = new GroovyClass-Loader();
Class greetingClass = gcl.parseClass( new File( “DSL.groovy”));
GroovyObject dsl = (GroovyObject)greetingClass.newInstance();
dsl.setMetaClass(myCustomDSLMetaClass);
DSL 설계시 고려사항
DSL 적용• 강제로 적용하지 말 것 !
• 대신 ...–사용자들이 자신들만의 DSL 을 만들도록 할 것–정기적으로 최종 사용자를 포함시킬 것–어떻게 DSL 을 사용하고 있는지 검토할 것
–사용자들에게 어떤 것이 되고 안 되는지 알려줄 것
씻고 , 닦고 , 반복• 반복 프로세스–간단하게 시작
–처음에 원하는 것을 얻을 수 없음을 상기
–관련 전문가와 브레인스토밍하며 지속적으로 개선 시켜야 함 !
방어 프로그래밍• DSL 은 샌드박스에서 실행해야 한다 .–사용자들이 애플리케이션을 다운시켜서는 안됨
• 테스트 , 테스트 , 테스트 !–우아하게 실패할 것–유효하지 않은 경우 : 에러 테스트 !–의미 있는 에러 메시지를 출력할 것
요약• DLS 은 해당 분야 전문가 domain xpert 와 개발자 사이에
공통의 상징 metaphor 으로서 공유할 수 있는 훌륭한 툴
• DSL 은 기존의 틀에 박힌 코드 없이 범주 domain 개념을 표현
• Groovy 문법과 동적 특성으로 DSL 를 쉽게 만들고 Java 애플리케이션 통합
• 고품질 적용을 고려하여 반복적 iterative 이고 방어적인 프로그래밍 .
참고자료• JavaZone 2008 Groovy DSL
– Guillaum Laforge, G2One
• Design you own Domain-Specific Language• Guillaum Laforge, SpringSource, SpringOne 2GX
• Groovy in Action– Dierk König, Guillaum Laforge, Manning Publications
• Programming Groovy– Vankat Subramaniam, PragramaticBookshelf
• Groovy 오픈캐스트– http://opencast.naver.com/gr386