주문을 하거나, 시나리오가 좀 복잡한 캡슐을 개발하다 보면, 예외처리가 필요한 경우가 있습니다.
특정 액션을 수행하다가, 뭔가 문제가 발생했을때 (exception) 다른 액션을 대신 수행 시키는 방법입니다.
fail library의 checkedError 함수와 replan을 활용하는 것인데요.
replan을 하면, 다른 액션을 수행시킬 수 있습니다만, 조건이 있습니다.
A의 액션 수행중 B 액션으로 replan을 할 경우, A와 B의 아웃풋 컨셉이 동일해야 합니다.
+ 그리고 B의 아웃풋 컨셉이 A의 아웃풋 컨셉을 extends 한 컨셉일 때도 가능합니다. (반대는 안됨)
활용 예시를 하나 들어서 캡슐을 만들어 볼 건데요.
마트에서 주문을 시작하면, 메뉴 리스트를 보여주고(ShowMenu), continue 발화로 카트에 담는 것(AddItem)입니다.
메뉴 리스트에서 메뉴를 담으면, 자동으로 카트 화면을 보여주고(ShowCart) 카트에는 현재 담겨져 있는 메뉴들을 보여줍니다.
! 그런데, 메뉴 리스트에 없는 아이템을 말하면 (exception), 해당 메뉴는 없다는 메시지와 함께, 메뉴 화면을 다시 보여주게 하고자 합니다. (replan ShowMenu)
그래서, ShowMenu, ShowCart, AddItem 액션 모두 output 컨셉은 Order 라고 정의를 했습니다.
(Transaction 캡슐을 만드시게 되면 보통 이렇게 합니다.)
그리고 CreateOrder 라는 액션으로 초기 Order 컨셉을 생성합니다. 메뉴 리스트도 여기서 만들어 두고 있습니다.
메뉴를 담는 액션인 AddItem 액션 모델링입니다.
action (AddItem) {
description (__DESCRIPTION__)
type (Search)
collect {
input (order) {
type (Order)
min(Required) max(One)
default-init {
intent {
goal : CreateOrder
}
}
}
input (userRequest) {
type (UserRequest)
min (Required) max (One)
}
}
output (Order) {
throws {
error (GoShowCart) {
property (resultOrder) {
type (Order)
min (Required) max (One)
}
on-catch {
replan {
intent {
goal : ShowCart
value : $expr(resultOrder)
}
}
}
}
error (GoShowMenu) {
property (resultOrder) {
type (Order)
min (Required) max (One)
}
on-catch {
replan {
intent {
goal : ShowMenu
value : $expr(resultOrder)
}
}
}
}
}
}
}
Order 컨셉은 일종의 Context 또는 주문정보 자체라고 보시면 되고, 이 컨셉을 액션간에 계속 넘겨주면서 정보를 유지하게 합니다.
AddItem 액션은, Order와 userRequest (주문요청 정보)를 받아서 처리하는데요.
Output 블럭 안에 throw 블럭을 유심히 보셔야 합니다. js 로직에서 던지는 exception을 받아서 처리하는 부분입니다.
AddItem.js 로직을 보겠습니다.
function addItem (order, userRequest) {
if (!order.orderedItems) {
order.orderedItems = [];
}
if (userRequest) {
if (!userRequest.quantity) {
userRequest.quantity = 1;
}
var itemExist = false;
if (order.menuItems.find(item => {
return item.productName.toString() == userRequest.productName.toString()
})) {
itemExist = true;
}
if (itemExist) {
order.orderedItems.push({
productName : userRequest.productName,
quantity : userRequest.quantity
})
var cnt = 0;
order.orderedItems.forEach( item => {
cnt = cnt + item.quantity;
})
order.itemCount = cnt;
console.log("item exists")
throw fail.checkedError("Add success. show cart", "GoShowCart", {resultOrder:order});
} else {
order.errorMessage = '말씀하신 상품은 없네요'
console.log("item NOT exists")
throw fail.checkedError("Add failed. show menu", "GoShowMenu", {resultOrder:order});
}
}
// should not be here
return order;
}
메뉴 리스트에서 유저가 요청한 메뉴명이 있는지 찾아본 뒤에 있으면 카트에 추가한 뒤에 카트 보여주기 액션(ShowCart)으로 보내고, 없으면 에러 메시지를 넣은 다음에, 메뉴 보여주기 액션(ShowMenu)으로 보냅니다.
* 이 js 코드는 특이하게도, return을 하지 않습니다. 항상 checkedError를 통해서 익셉션으로 처리하게 하고 있습니다.
이때, throw fail.checkedError 를 사용합니다. 파라미터중에서, 첫번째는 로그에 나오게될 메시지이고,
2번째는 exception이름인데, 앞에 보여드렸던 AddItem 모델코드에 throw 블럭에 정의한 이름입니다.
3번째 파라미터는, exception을 던질때, 같이 보내줄 데이터들을 json 형태로 보냅니다. order를 resultOrder라는 이름으로 전달했고, 이 역시 모델링 코드에 동일한 이름으로 정의가 되어 있어야 합니다.
* 3번째 파라미터는 json 형태로, 1개의 컨셉만 보낼수 있는건 아니고, 2개 이상도 보낼 수 있습니다.
{ resultOrder:order, myItem:item} 처럼 쓰시면 됩니다.
resultOrder로 이름을 바꾼 이유는 모델링 코드에서는 이미 order 라는 이름이 인풋으로 있기때문에 겹치지 않도록 바꾸었습니다.
실행을 해보면,
"메뉴 보여줘" 라는 발화를 하면, ShowMenu가 시작되고, 메뉴 리스트를 보여줍니다.
딸기 우유 1개를 추가했더니, 카트에 담기고, 카트화면이 나옵니다.
"메뉴 보여줘" 발화를 해서 다시 메뉴 화면으로 이동합니다. 카트에 아이템은 그대로 유지되고 있습니다.
이번엔 메뉴 리스트에 없는 "초코 사탕"을 추가하려고 해보았습니다만, 메뉴에 없어서, 에러 메시지를 보여주고, 카트가 아닌 메뉴 화면으로 되돌아 갔습니다.
이번엔 메뉴에 있는 걸로 다시 담아 보겠습니다. 카트에 잘 담기고, 정상 메시지도 보이고 있습니다.
전체 코드는 아래 위치에 있으니 참고하세요~
https://github.com/earthworm925/bixby/tree/mart-checkederror-replan
'Bixby 개발' 카테고리의 다른 글
효율적인 학습 방법 - 단어(vocab) 쪼개기 (0) | 2019.12.10 |
---|---|
인풋 프롬프트 (Input Prompt)에서 여러 컨셉 또는 Structure를 받기 (0) | 2019.11.07 |
Bixby 캡슐 개발하기 (0) | 2019.11.07 |