Post

🦊 chap10. λžŒλ‹€λ₯Ό μ΄μš©ν•œ 도메인 μ „μš© μ–Έμ–΄

chap10. λžŒλ‹€λ₯Ό μ΄μš©ν•œ 도메인 μ „μš© μ–Έμ–΄

10.1 도메인 μ „μš© μ–Έμ–΄

DSL은 νŠΉμ • λΉ„μ¦ˆλ‹ˆμŠ€ λ„λ©”μΈμ˜ 문제λ₯Ό ν•΄κ²°ν•˜λ €κ³  λ§Œλ“  언어이닀.

DSLμ΄λž€ νŠΉμ • λΉ„μ¦ˆλ‹ˆμŠ€ 도메인을 μΈν„°νŽ˜μ΄μŠ€λ‘œ λ§Œλ“  API라고 생각할 수 μžˆλ‹€.

  • μ½”λ“œμ˜ μ˜λ„κ°€ λͺ…ν™•νžˆ μ „λ‹¬λ˜μ–΄μ•Ό ν•˜λ©° ν”„λ‘œκ·Έλž˜λ¨Έκ°€ μ•„λ‹Œ μ‚¬λžŒλ„ 이해할 수 μžˆμ–΄μ•Ό ν•œλ‹€.
  • 가독성은 μœ μ§€λ³΄μˆ˜μ˜ 핡심이닀

10.1.1 DSL의 μž₯점과 단점

  • μž₯점
    • 간결함: APIλŠ” λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ μΊ‘μŠν™”ν•˜μ—¬ λ°˜λ³΅μ„ ν”Όν•˜κ³  μ½”λ“œλ₯Ό κ°„κ²°ν•˜κ²Œ ν•œλ‹€
    • 가독성: λΉ„ 도메인 전문가도 μ½”λ“œλ₯Ό μ‰½κ²Œ 이해할 수 μžˆλ‹€
    • μœ μ§€λ³΄μˆ˜: 잘 μ„€κ³„λœ DSL둜 κ΅¬ν˜„ν•œ μ½”λ“œλŠ” μ‰½κ²Œ μœ μ§€λ³΄μˆ˜ν•˜κ³  λ°”κΏ€ 수 μžˆλ‹€
    • 높은 μˆ˜μ€€μ˜ 좔상화: 좔상화 μˆ˜μ€€μ—μ„œ λ™μž‘ν•˜λ―€λ‘œ 도메인과 μ§μ ‘μ μœΌλ‘œ κ΄€λ ¨λ˜μ§€ μ•Šμ€ 세뢀사항을 μˆ¨κΈ΄λ‹€
    • 집쀑: ν”„λ‘œκ·Έλž˜λ¨Έκ°€ νŠΉμ • μ½”λ“œμ— 집쀑할 수 있고 결과적으둜 생산성이 쒋아진닀
    • κ΄€μ‹¬μ‚¬μ˜ 뢄리: μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ 인프라 ꡬ쑰와 κ΄€λ ¨λœ λ¬Έμ œμ™€ λ…λ¦½μ μœΌλ‘œ λΉ„μ¦ˆλ‹ˆμŠ€ κ΄€λ ¨λœ μ½”λ“œμ— μ§‘μ€‘ν•˜κΈ° μš©μ΄ν•˜λ‹€. 결과적으둜 μœ μ§€λ³΄μˆ˜κ°€ μ‰¬μš΄ μ½”λ“œλ₯Ό κ΅¬ν˜„ν•œλ‹€.
  • 단점
    • μ„€κ³„μ˜ 어렀움: κ°„κ²°ν•˜κ²Œ μ œν•œμ μΈ 언어에 도메인 지식을 λ‹΄λŠ” 것은 쉽지 μ•Šλ‹€
    • 개발 λΉ„μš©: 초기 ν”„λ‘œμ νŠΈμ— λ§Žμ€ λΉ„μš©κ³Ό μ‹œκ°„μ΄ μ†Œλͺ¨λ˜λ©° μœ μ§€λ³΄μˆ˜μ™€ 변경은 뢀담을 μ£ΌλŠ” μš”μ†Œμ΄λ‹€
    • μΆ”κ°€ 우회 계측: DSL은 μΆ”κ°€ κ³„μΈ΅μœΌλ‘œ 도메인 λͺ¨λΈμ„ 감싸며 계측을 μ΅œλŒ€ν•œ μž‘κ²Œ λ§Œλ“€μ–΄ μ„±λŠ₯ 문제λ₯Ό νšŒν”Όν•΄μ•Ό ν•œλ‹€.
    • μƒˆλ‘œ λ°°μ›Œμ•Όν•˜λŠ” μ–Έμ–΄: νŒ€μ΄ λ°°μ›Œμ•Όν•˜λŠ” μ–Έμ–΄κ°€ ν•œ 개 더 λŠ˜μ–΄λ‚œλ‹€λŠ” 뢀담이 μžˆλ‹€
    • ν˜ΈμŠ€νŒ… μ–Έμ–΄ ν•œκ³„: μž₯ν™©ν•œ ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄λ₯Ό 기반으둜 λ§Œλ“  DSL은 μ„±κ°€μ‹  λ¬Έλ²•μ˜ μ œμ•½μ„ λ°›κ³  읽기가 μ–΄λ €μ›Œμ§„λ‹€.

10.1.2 JVMμ—μ„œ μ΄μš©ν•  수 μžˆλŠ” λ‹€λ₯Έ DSL ν•΄κ²°μ±…

λ‚΄λΆ€ DSL

μžλ°” κΈ°μ€€μœΌλ‘œ μžλ°”λ‘œ κ΅¬ν˜„ν•œ DSL을 μ˜λ―Έν•œλ‹€.

μž₯점

  • κΈ°μ‘΄ μžλ°” μ–Έμ–΄λ₯Ό μ΄μš©ν•˜λ©΄ μ™ΈλΆ€ DSL에 λΉ„ν•΄ μƒˆλ‘œμš΄ κΈ°μˆ μ— λŒ€ν•œ λ…Έλ ₯이 ν˜„μ €ν•˜κ²Œ 쀄어든닀.
  • λ‹€λ₯Έ μ–Έμ–΄μ˜ 컴파일러λ₯Ό μ΄μš©ν•˜κ±°λ‚˜ μ™ΈλΆ€ DSL 도ꡬ에 λŒ€ν•œ μΆ”κ°€ λΉ„μš©μ΄ 듀지 μ•ŠλŠ”λ‹€
  • μƒˆλ‘œμš΄ μ–Έμ–΄λ₯Ό λ°°μš°μ§€ μ•Šμ•„λ„ λœλ‹€
  • κΈ°μ‘΄ μžλ°” IDE의 μžλ™ μ™„μ„±, μžλ™ λ¦¬νŒ©ν„°λ§ 같은 κΈ°λŠ₯을 κ·ΈλŒ€λ‘œ μ‚¬μš©ν•  수 μžˆλ‹€.
  • μΆ”κ°€ DSL을 κ°œλ°œν•΄μ•Ό ν•˜λŠ” 상황에 μ‰½κ²Œ ν•©μΉ  수 μžˆλ‹€.

닀쀑 DSL

JVMμ—μ„œ μ‹€ν–‰λ˜λŠ” μ–Έμ–΄λŠ” 100κ°œκ°€ λ„˜λŠ”λ‹€.

단점

  • μƒˆλ‘œμš΄ ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄λ₯Ό λ°°μš°κ±°λ‚˜ 이미 ν•΄λ‹Ή κΈ°μˆ μ„ 가지고 μžˆμ–΄μ•Ό ν•œλ‹€.
  • μ—¬λŸ¬ 컴파일러둜 μ†ŒμŠ€λ₯Ό λΉŒλ“œν•˜λ„λ‘ λΉŒλ“œ 과정을 κ°œμ„ ν•΄μ•Ό ν•œλ‹€.
  • μžλ°”μ™€ ν˜Έν™˜μ„±μ΄ μ™„λ²½ν•˜μ§€ μ•Šμ•„ μ„±λŠ₯ 손싀이 λ°œμƒν•  μˆ˜λ„ μžˆλ‹€.

μ™ΈλΆ€ DSL

μžμ‹ λ§Œμ˜ 문법과 ꡬ문으둜 μƒˆ μ–Έμ–΄λ₯Ό 섀계해야 ν•œλ‹€.

μž₯점은 λ¬΄ν•œν•œ μœ μ—°μ„±μ„ μ œκ³΅ν•œλ‹€λŠ” κ²ƒμ΄μ§€λ§Œ, λΉ„μ¦ˆλ‹ˆμŠ€ μ½”λ“œμ˜ λΆ„λ¦¬λ‘œ 인곡 계측이 생기기 λ•Œλ¬Έμ— μ–‘λ‚ μ˜ κ²€κ³Ό κ°™λ‹€.

10.2 μ΅œμ‹  μžλ°” API의 μž‘μ€ DSL

  • λ„€μ΄ν‹°λΈŒ μžλ°” API

μžλ°” 8μ—μ„œ λžŒλ‹€μ™€ λ©”μ„œλ“œ μ°Έμ‘°λ₯Ό 톡해 μž¬μ‚¬μš©μ„±κ³Ό λ©”μ„œλ“œ 결합도가 λ†’μ•„μ‘Œλ‹€.

10.2.1 슀트림 APIλŠ” μ»¬λ ‰μ…˜μ„ μ‘°μž‘ν•˜λŠ” DSL

Stream μΈν„°νŽ˜μ΄μŠ€λŠ” λ„€μ΄ν‹°λΈŒ μžλ°” API에 μž‘μ€ λ‚΄λΆ€ DSL을 μ μš©ν•œ 쒋은 μ˜ˆμ‹œμ΄λ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
List<String> errors = new ArrayList<>();
int errorCount = 0;
BufferedReader bufferedReader
	= new BufferedReader(new FileReader(fileName));
String line = bufferedReader.readLine();
while (errorCount < 40 && line != null) {
	if (line.startWith("ERROR")) {
		errors.add(line);
		errorCount++;
	}
	line = bufferedReader.readLine();
}

μ½”λ“œκ°€ μž₯황에 μ˜λ„λ₯Ό ν•œλˆˆμ— νŒŒμ•…ν•˜κΈ° μ–΄λ €μš΄ μ½”λ“œμ΄λ‹€.

이λ₯Ό Stream μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ΄μš©ν•΄ ν•¨μˆ˜ν˜•μœΌλ‘œ κ΅¬ν˜„ν•˜λ©΄ 쉽고 κ°„κ²°ν•˜κ²Œ μ½”λ“œλ₯Ό κ΅¬ν˜„ν•  수 μžˆλ‹€.

1
2
3
4
List<String> errors = Files.lines(Paths.get(fileName)
													 .filter(line -> line.startsWith("ERROR"))
													 .limit(40)
													 .collect(toList());

슀트림 API의 ν”Œλ£¨μ–ΈνŠΈ ν˜•μ‹μ€ 잘 μ„€κ³„λœ DSL의 νŠΉμ§•μ΄λ‹€.

10.2.2 데이터λ₯Ό μˆ˜μ§‘ν•˜λŠ” DSL인 Collectors

Collector μΈν„°νŽ˜μ΄μŠ€λŠ” 데이터 μˆ˜μ§‘μ„ μˆ˜ν–‰ν•˜λŠ” DSL둜 κ°„μ£Όν•  수 μžˆλ‹€.

κ·Έλ£Ήν™” 평가 μˆœμ„œκ°€ λ‹¬λΌμ§ˆ 수 μžˆλŠ”λ° μ΄λŠ” GroupingBuilder λ₯Ό λ§Œλ“€μ–΄ μž‘μ—…μ„ μœ„μž„ν•˜λ©΄ ν•΄κ²°ν•  수 μžˆλ‹€.

10.3 μžλ°”λ‘œ DSL을 λ§Œλ“œλŠ” νŒ¨ν„΄κ³Ό 기법

10.3.1 λ©”μ„œλ“œ 체인

DSLμ—μ„œ κ°€μž₯ ν”ν•œ 방식이닀. 이λ₯Ό μ΄μš©ν•˜λ©΄ ν•œ 개의 λ©”μ„œλ“œ 호좜 체인으둜 거래 주문을 μ •μ˜ν•  수 μžˆλ‹€.

1
2
3
4
5
6
7
8
9
10
Order order = forCustomer("BigBank")
			.buy(80)
			.stock("IBM")
			.on("NYSE")
			.at(125.00)
			.sell(50)
			.stock("GOOGLE")
			.on("NASDAQ")
			.at(375.00)
			.end();

이λ₯Ό μœ„ν•΄ ν”Œλ£¨μ–ΈνŠΈ API둜 도메인 객체λ₯Ό λ§Œλ“œλŠ” λͺ‡ 개의 λΉŒλ”λ₯Ό κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MethodChainingOrderBuilder {

		public final Order order = new Order();

		private MethodChainingOrderBuilder(String customer) {
				order.setCustomer(customer);
		}

		public static MethodChainingOrderBuilder forCustomer(String customer) {
				return new MethodChainingOrderBuilder(customer);
		}

		public TradeBuilder buy(int quantity) {
				return new TradeBuilder(this, Trade.Type.BUY, quantity);
		}

		public TradeBuilder sell(int quantity) {
				return new TradeBuilder(this, Trade.Type.SELL, quantity);
		}

		public MethodChainingOrderBuilder addTrade(Trade trade) {
				order.addTrade(trade);
				return this;
		}

		public Order end() {
				return order;
		}
}

buy, sell λ©”μ„œλ“œλŠ” λ‹€λ₯Έ 주문을 λ§Œλ“€μ–΄ μΆ”κ°€ν•  수 μžˆλ„λ‘ μžμ‹ μ„ λ§Œλ“€μ–΄ λ°˜ν™˜ν•œλ‹€

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TradeBuilder {
		private final MethodChainingOrderBuilder builder;
		public final Trade trade = new Trade();

		private TradeBuilder(MethodChainingOrderBuilder builder,
												 Trade.Type type, int quantity) {
				this.builder = builder;
				trade.setType(type);
				trade.setQuantity(quantity);
		}

		public StockBuilder stock(String symbol) {
				return new StockBuilder(builder, trade, symbol);
		}
}

Stock 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“œλŠ” TradeBuilder 의 곡개 λ©”μ„œλ“œλ₯Ό μ΄μš©ν•΄μ•Ό ν•œλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class StockBuilder {
		private final MethodChainingOrderBuilder builder;
		private final Trade trade;
		private final Stock stock = new Stock();

		private StockBuilder(MethodChainingOrderBuilder builder,
												 Trade trade, String symbol) {
				this.builder = builder;
				this.trade = trade;
				stock.setSymbol(symbol);
		}

		public TradeBuilderWithStock on(String market) {
				stock.setMarket(market);
				trade.setStock(stock);
				return new TradeBuilderWithStock(builder, trade);
		}
}

주식 μ‹œμž₯을 μ§€μ •ν•˜κ³  κ±°λž˜μ— 주식을 μΆ”κ°€ν•˜κ³  μ΅œμ’… λΉŒλ”λ₯Ό λ°˜ν™˜ν•˜λŠ” on λ©”μ„œλ“œ ν•œ 개λ₯Ό μ •μ˜ν•œλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TradeBuilderWithStock {
		private final MethodChainingOrderBuilder builder;
		private final Trade trade;

		public TradeBuilderWithStock(MethodChainingOrderBuilder builder, Trade trade) {
				this.builder = builder;
				this.trade = trade;
		}

		public MethodChainingOrderBuilder at(double price) {
				trade.setPrice(price);
				return builder.addTrade(trade);
		}
}

μ‚¬μš©μžκ°€ 미리 μ§€μ •λœ μ ˆμ°¨μ— 따라 ν”Œλ£¨μ–ΈνŠΈ API의 λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ„λ‘ κ°•μ œν•˜μ—¬ κΈ°μ‘΄ 거래λ₯Ό μ˜¬λ°”λ‘œ μ„€μ •ν•˜κ²Œ λœλ‹€.

λ‹€λ§Œ μƒμœ„ μˆ˜μ€€μ˜ λΉŒλ”λ₯Ό ν•˜μœ„ μˆ˜μ€€μ˜ λΉŒλ”μ™€ μ—°κ²°ν•  μ ‘μ°© λ§Žμ€ μ½”λ“œκ°€ ν•„μš”ν•˜λ‹€λŠ” 것이 단점이닀.

10.3.2 μ€‘μ²©λœ ν•¨μˆ˜ 이용

닀름 ν•¨μˆ˜ μ•ˆμ— ν•¨μˆ˜λ₯Ό μ΄μš©ν•΄ 도메인 λͺ¨λΈμ„ λ§Œλ“œλŠ” DSL νŒ¨ν„΄μ΄λ‹€.

1
2
3
4
Order oder = order("BigBank",
									buy(80, stock("IBM", on("NYSE")), at(125.00)),
									sell(50, stock("GOOGLE", on("NASDAQ")), at(375.00))
								);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class NestedFunctionOrderBuilder {

		public static Order order(String customer, Trade... trades) {
				Order order = new Order()
				order.setCustomer(customer);
				Stream.of(trades).forEach(order::addTrade); // 주문에 λͺ¨λ“  거래 μΆ”κ°€
				return order;
		}

		public static Trade buy(int quantity, Stock stock, double price) {
				return buildTrade(quantity, stock, price, Trade.Type.BUY);
		}

		public static Trade sell(int quantity, Stock stock, double price) {
				return buildTrade(quantity, stock, price, Trade.Type.SELL);
		}

		private static Trade buildTrade(int quantity, Stock stock, double price, Trade.Type buy) {
				Trade trade = new Trade();
				trade.setQuantity(quantity);
				trade.setType(buy);
				trade.setStock(stock);
				trade.setPrice(price);
				return trade;
		}

		public static double at(double price) {
				return price;
		}

		public static Stock stock(String symbol, String market) {
				Stock stock = new Stock();
				stock.setSymbol(symbol);
				stock.setMarket(market);
				return stock;
		}

		public static String on(String market) {
				return market;
		}
}

λ©”μ„œλ“œ 체이닝에 λΉ„ν•΄ κ΅¬ν˜„μ΄ κ°„λ‹¨ν•˜λ‹€.

ν•˜μ§€λ§Œ κ²°κ³Ό DSL에 더 λ§Žμ€ κ΄„ν˜Έλ₯Ό μ‚¬μš©ν•΄μ•Ό ν•˜κ³  인수 λͺ©λ‘μ„ 정적 λ©”μ„œλ“œμ— λ„˜κ²¨μ€˜μ•Ό ν•œλ‹€λŠ” μ œμ•½μ΄ μžˆλ‹€.

인수 μƒλž΅μ— λŒ€ν•œ κ°€λŠ₯성을 μ²˜λ¦¬ν•  수 μžˆλ„λ‘ μ—¬λŸ¬ λ©”μ„œλ“œ μ˜€λ²„λΌμ΄λ“œλ₯Ό κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€.

10.3.3 λžŒλ‹€ ν‘œν˜„μ‹μ„ μ΄μš©ν•œ ν•¨μˆ˜ μ‹œν€€μ‹±

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class LambdaOrderBuilder {
		private Order order = new Order();

		public static Order order(Consumer<LambdaOrderBuilder> consumer) {
				LambdaOrderBuilder builder = new LambdaOrderBulider();
				consumer.accept(builder)
				return builder.order;
		}

		public void forCustomer(String customer) {
				order.setCustomer(customer);
		}

		public void buy(Consumer<TradeBuilder> consumer) {
				trade(consumer, Trade.Type.BUY);
		}

		public void sell(Consumer<TradeBuilder> consumer) {
				trade(consumer, Trade.Type.SELL);
		}

		private void trade(Consumer<TradeBuilder> consumer, Trade.Type type) {
				TradeBuilder builder = new TradeBulder();
				builder.trade.setType(type);
				consumer.accept(builder);
				order.addTrade(builder.trade);
		}
}

buy sell λ©”μ„œλ“œλŠ” 두 개의 Consumer λžŒλ‹€ ν‘œν˜„μ‹μ„ λ°›κ³ , 이λ₯Ό μ‹€ν–‰ν•˜λ©΄ κ±°λž˜κ°€ λ§Œλ“€μ–΄μ§„λ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TradeBuilder {
		private Trade trade = new Trade();

		public void quantity(int quantity) {
				trade.setQuantity(quantity);
		}

		public void price(double price) {
				trade.setPrice(price);
		}

		public void stock(Consumer<StockBuilder> consumer) {
				StockBuilder builder = new StockBuilder();
				consumer.accept(builder);
				trade.setStock(builder.stock);
		}
}
1
2
3
4
5
6
7
8
9
10
11
public class StockBuilder {
		private Stock stock = new Stock();

		public void symbol(String symbol) {
				stock.setSymbol(symbol);
		}

		public void market(String market) {
				stock.setMarket(market);
		}
}

λ©”μ„œλ“œ 체인 νŒ¨ν„΄μ²˜λŸΌ ν”Œλ£¨μ–ΈνŠΈ λ°©μ‹μœΌλ‘œ 거래 주문을 μ •μ˜ν•  수 있고,

쀑첩 ν•¨μˆ˜ ν˜•μ‹μ²˜λŸΌ λ‹€μ–‘ν•œ λžŒλ‹€ ν‘œν˜„μ‹μ˜ 쀑첩 μˆ˜μ€€κ³Ό λΉ„μŠ·ν•˜κ²Œ 도메인 객체의 계측 ꡬ쑰λ₯Ό μœ μ§€ν•œλ‹€.

λ‹€λ§Œ DSL μžμ²΄κ°€ μžλ°” 8 λžŒλ‹€ ν‘œν˜„μ‹ 문법에 μ˜ν•œ 작음의 영ν–₯을 λ°›κ²Œ λœλ‹€.

10.3.4 μ‘°ν•©ν•˜κΈ°

1
2
3
4
5
6
7
8
9
10
Order order =
		forCustomer("BigBank",
								buy(t -> t.quantity(80
													.stock("IBM") // 거래 객체λ₯Ό λ§Œλ“œλŠ” λžŒλ‹€ ν‘œν˜„μ‹ λ°”λ””μ˜ λ©”μ„œλ“œ 체인
													.on("NYSE")
													.at(125.00)),
								sell(t -> t.quantity(50)
													 .stock("GOOGLE")
													 .on("NASDAQ")
													 .at(125.00)) );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MixedBuilder {

		public static Order forCustomer(String customer, TradeBuilder... builders) {
				Order order = new Order();
				order.setCustomer(customer);
				Stream.of(builders).forEach(b -> order.addTrade(b.trade));
				return order;
		}

		public static TradeBuilder buy(Consumer<TradeBuilder> consumer) {
				return buildTrade(consumer, Trade.Type.BUY);
		}

		public static TradeBuilder sell(Consumer<TradeBulder> consumer) {
				return buildTrade(consumer, Trade.Type.SELL);
		}

		private static TradeBuilder buildTrade(Consumer<TradeBuilder> consumer, Trade.Type type) {
				TradeBuilder builder = new TradeBuilder();
				builder.trade.setType(buy);
				consumer.accept(builder);
				return builder;
		}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class TradeBuilder {
		private Trade trade = new Trade();

		public TradeBuilder quantity(int quantity) {
				trade.setQuantity(quantity);
				return this;
		}

		public TradeBuilder at(double price) {
				trade.setPrice(price);
				return this;
		}

		public StockBuilder stock(String symbol) {
				return StockBuilder(this, trade, symbol);
		}
}

public class StockBuilder {
		private final TradeBuilder builder;
		private final Trade trade;
		private final Stock stock = new Stock();

		private StockBuilder(TradeBuilder builder, Trade trade, String symbol){
				this.builder = builder;
				this.trade = trade;
				stock.setSymbol(symbol);
		}

		public TradeBuilder on(String market) {
				stock.setMarket(market);
				trade.setStock(stock);
				return builder;
		}
}

μ„Έ 가지 DSL νŒ¨ν„΄μ„ ν˜Όμš©ν•΄ 가독성 μžˆλŠ” DSL을 λ§Œλ“œλŠ” 방법이 μžˆλ‹€.

μ—¬λŸ¬ νŒ¨ν„΄μ˜ μž₯점을 ν™œμš©ν•  수 μžˆμ§€λ§Œ μ—¬λŸ¬ 가지 기법을 ν˜Όμš©ν•˜κ³  있기 λ•Œλ¬Έμ— μ‚¬μš©μžκ°€ DSL을 λ°°μš°λŠ”λ° 였랜 μ‹œκ°„μ΄ 걸리게 λœλ‹€.

10.3.5 DLS에 λ©”μ„œλ“œ μ°Έμ‘° μ‚¬μš©ν•˜κΈ°

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TaxCalculator {
		private boolean useRegional;
		private boolean useGeneral;
		private boolean useSurcharge;

		public TaxCalculator withTaxRegional() {
				useRegoinal = true;
				return this;
		}

		public TaxCalculator withTaxGeneral() {
				useGeneral = true;
				return this;
		}

		public TaxCalculator withTaxSurcharge() {
				useSurcharge = true;
				return this;
		}

		public double calculate(Order order) {
				return calculate(order, useRegional, useGeneral, useSurcharge);
		}
}
1
2
3
double value = new TaxCalculator().withTaxRegional()
																	.withTaxSurcharge()
																	.calculate(order)

TaxCalculator λŠ” 지역 μ„ΈκΈˆκ³Ό μΆ”κ°€ μš”κΈˆμ„ 주문에 μΆ”κ°€ν•˜κ³  μ‹Άλ‹€λŠ” 점을 λͺ…ν™•ν•˜κ²Œ λ³΄μ—¬μ£Όμ§€λ§Œ

μ½”λ“œκ°€ μž₯ν™©ν•˜λ‹€λŠ” 것이 λ¬Έμ œμ΄λ‹€.

μžλ°”μ˜ ν•¨μˆ˜ν˜• κΈ°λŠ₯을 μ΄μš©ν•˜λ©΄ 더 κ°„κ²°ν•˜κ³  μœ μ—°ν•œ λ°©μ‹μœΌλ‘œ 가독성을 달성할 수 μžˆλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
public class TaxCalculator {
		public DoubleUnaryOperator taxFunction = d -> d;

		public TaxCalculator with(DoubleUnaryOperator f) {
				taxFunction = taxFunction.andThen(f);
				return this;
		}

		public double calculate(Order order) {
				return taxFunction.applyAsDouble(order.getValue());
		}
}
1
2
3
double value = new TaxCalculator().with(Tax::regional)
																	.with(Tax::surcharge)
																	.calculate(order);

이 기법은 주문의 총 합에 μ μš©ν•  ν•¨μˆ˜ ν•œ 개의 ν•„λ“œλ§Œ ν•„μš”λ‘œν•˜λ©°, TaxCalculator 클래슀λ₯Ό 톡해 λͺ¨λ“  μ„ΈκΈˆ 섀정이 μ μš©λœλ‹€.

10.4 μ‹€μƒν™œμ˜ μžλ°” 8 DSL

10.4.1 jOOQ

SQL을 κ΅¬ν˜„ν•˜λŠ” 내뢀적 DSL둜 μžλ°”μ— 직접 λ‚΄μž₯된 ν˜•μ‹ μ•ˆμ • 언어이닀.

λ°μ΄ν„°λ² μ΄μŠ€ μŠ€ν‚€λ§ˆλ₯Ό μ—­κ³΅ν•™ν•˜λŠ” μ†ŒμŠ€μ½”λ“œ 생성기 덕뢄에 μžλ°” μ»΄νŒŒμΌλŸ¬κ°€ λ³΅μž‘ν•œ SQL ꡬ문의 ν˜•μ‹μ„ 확인할 수 μžˆλ‹€.

1
2
3
create.selectFrom(BOOK)
			.where(BOOK.PUBLISHED_IN.eq(2016))
			.orderBy(BOOK.TITLE)

슀트림 API와 μ‘°ν•©ν•΄ μ‚¬μš©ν•  수 μžˆλ‹€λŠ” 것이 λ˜λ‹€λ₯Έ μž₯점이닀.

10.4.2 큐컴버

BDD(λ™μž‘ 주도 개발)λŠ” ν…ŒμŠ€νŠΈ 주도 개발의 ν™•μž₯으둜 λ‹€μ–‘ν•œ λΉ„μ¦ˆλ‹ˆμŠ€ μ‹œλ‚˜λ¦¬μ˜€λ₯Ό ꡬ쑰적으둜 μ„œμˆ ν•˜λŠ” κ°„λ‹¨ν•œ 도메인 μ „μš© μŠ€ν¬λ¦½νŒ… μ–Έμ–΄λ₯Ό μ‚¬μš©ν•œλ‹€.

큐컴버 μŠ€ν¬λ¦½νŒ… μ–Έμ–΄λ‘œ κ°„λ‹¨ν•œ λΉ„μ¦ˆλ‹ˆμŠ€ μ‹œλ‚˜λ¦¬μ˜€λ₯Ό μ •μ˜ν•  수 μžˆλ‹€.

1
2
3
4
5
Feature: Buy stock
	Senario: Buy 10 IBM stocks
		Given the price of a "IBM" stock is 125$
		When I buy 10 "IBM"
		Then the order value should be 1250$

μ „μ œμ‘°κ±΄ μ •μ˜ Given

μ‹œν—˜ν•˜λ €λŠ” 도메인 객체의 μ‹€μ§ˆ 호좜 When

ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€μ˜ κ²°κ³Όλ₯Ό ν™•μΈν•˜λŠ” Assertion Then

μ„Έ κ°€μ§€λ‘œ κ΅¬λΆ„λ˜λŠ” κ°œλ…μ„ μ‚¬μš©ν•œλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class BuyStocksSteps {
	private Map<String, Integer> stockUnitPrices = new HashMap<>();
	private Order order = new Order();

	@Given("^the price of a \"(.*?)\" stock is (\\d+\\$$")
	public void setUnitPrice(String stockName, int unitPrice) {
		stockUnitValues.put(stockName, unitPrice);
	}

	@When("\I buy (\\d+) \"(.*?)\"$")
	public void buyStocks(int quantity, String stockName) {
		...
	}

	@Then("^the Order value should be (\\d+)\\$$")
	public void checkOrderValue(int expectedValue) {
		assertEqulas(expectedValue, order.getValue());
	}
}

μžλ°” 8이 λžŒλ‹€ ν‘œν˜„μ‹μ„ μ§€μ›ν•˜λ©΄μ„œ 두 개의 인수 λ©”μ„œλ“œλ₯Ό μ΄μš©ν•΄ μ–΄λ…Έν…Œμ΄μ…˜μ„ μ œκ±°ν•˜λŠ” λ‹€λ₯Έ 문법을 κ°œλ°œν•  μˆ˜λ„ μžˆλ‹€.

1
2
3
4
5
6
7
8
9
10
11
public class BuyStocksSteps {
	private Map<String, Integer> stockUnitPrices = new HashMap<>();
	private Order order = new Order();

	public BuyStocksSteps() {
		Given("^the price of a \"(.*?)\" stock is (\\d+\\$$",
			(String stockName, int unitPrice) -> {
				stockUnitValues.put(stockName, unitPrice);
			});
		...
	}

ν…ŒμŠ€νŠΈ λ©”μ„œλ“œκ°€ 무λͺ… λžŒλ‹€λ‘œ λ°”λ€Œλ©΄μ„œ 의미λ₯Ό 가진 λ©”μ„œλ“œ 이름을 μ°ΎλŠ” 뢀담이 μ—†μ–΄μ‘Œλ‹€.

10.4.3 μŠ€ν”„λ§ 톡합

유λͺ…ν•œ μ—”ν„°ν”„λΌμ΄μ¦ˆ 톡합 νŒ¨ν„΄μ„ 지원할 수 μžˆλ„λ‘ μ˜μ‘΄μ„± μ£Όμž…μ— κΈ°λ°˜ν•œ μŠ€ν”„λ§ ν”„λ‘œκ·Έλž˜λ° λͺ¨λΈμ„ ν™•μž₯ν•œλ‹€.

μŠ€ν”„λ§ 기반 μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜ λ‚΄μ˜ κ²½λŸ‰μ˜ 원격, 메세징, μŠ€μΌ€μ₯΄λ§μ„ μ§€μ›ν•˜κ³  채널, μ—”λ“œν¬μΈνŠΈ, 폴러, 채널 인터셉터 λ“± 메세지 기반의 μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ ν•„μš”ν•œ κ°€μž₯ κ³΅ν†΅μ˜ νŒ¨ν„΄μ„ λͺ¨λ‘ κ΅¬ν˜„ν•œλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Configuration
@EnableIntegration
public class MyConfiguration {
	@Bean
	public MessageSource<?> integerMessageSource() {
		MethodInvokingMessageSource source = new MethodInvokingMessageSource();
		source.setObject(new AtomicInteger());
		source.setMethodName("getAndIncrement");
		return source;
	}

	@Bean
	public DirectChannel inputChannel() {
		return new DirectChannel();
	}

	@Bean
	public IntegrationFlow myFlow() {
		return IntegrationFlows
				.from(this.integerMessageSource(), c -> c.poller(Pollers.fixedRate(10)))
				.channel(this.inputChannel())
				.filter((Integer p) -> p % 2 == 0)
				.transform(Object::toString)
				.channel(MessageChannels.queue("queueChannel"))
				.get();
	}
}

λ©”μ„œλ“œ 체인 νŒ¨ν„΄μ„ κ΅¬ν˜„ν•˜λŠ” IntegrationFlows ν΄λž˜μŠ€κ°€ μ œκ³΅ν•˜λŠ” μœ μ—°ν•œ λΉŒλ”λ₯Ό μ‚¬μš©ν•œλ‹€.

ν”Œλ‘œμš°λŠ” κ³ μ • μ†λ„λ‘œ MessageSource λ₯Ό ν΄λ§ν•˜λ©΄μ„œ 일련의 μ •μˆ˜λ₯Ό μ œκ³΅ν•˜κ³ , ν™€μˆ˜λ₯Ό λ¬Έμžμ—΄λ‘œ λ³€ν™˜ν•΄ μ΅œμ’…μ μœΌλ‘œ κ²°κ³Όλ₯Ό 좜λ ₯ 채널에 μ „λ‹¬ν•œλ‹€.

This post is licensed under CC BY 4.0 by the author.