ask-sdkの設計を覗き見る

この記事はなんちゃらですってのは↑で！

さて、みなさんAlexaのスキル開発やっていますか？
僕は、必要にかられたときにちょいちょいやってるって感じです。

## なんでこの記事を書いているか

もともとv1で開発を行っているときに、もろもろの課題を感じていて結局フレームワークを自作してました。
ですが、同様の設計のものがv2としてリリースされたので書いてみようと思いました。

ちょいちょいルー大柴っぽくなってますがご愛嬌。

それではさっそく

## Builder Pattern

GoFの[ResponseFactory](https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/blob/3f52c59b00ba2110acfab5e440cd9832de3d92d3/ask-sdk-core/lib/response/ResponseFactory.ts)で使われています。
定義は[ResponseBuilder](https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/blob/3f52c59b00ba2110acfab5e440cd9832de3d92d3/ask-sdk-core/lib/response/ResponseBuilder.ts)にあるので、今回はこちらを参照します。

必要な部分だけを抜き出したものがこちら

```ts
export interface ResponseBuilder {
    speak(speechOutput : string) : this;
    reprompt(repromptSpeechOutput : string) : this;
    withSimpleCard(cardTitle : string, cardContent : string) : this;
    /** more */
    getResponse() : Response;
}
```

これはQueryBuilderなどでよく見られる[BuilderPattern](https://ja.wikipedia.org/wiki/Builder_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3)を採用しています。

AlexaのResponseは定義から少しでも外れるとエラーを吐いてしまうので、ここで抽象化し扱いやすくしています。
利用者はBuilderを使って柔軟にResponseを生成することが可能になりました。
組み立て工程では `this === ResponseBuilderのInstance` を返却してメソッドチェーンを行いやすくしており、最後に `getResponse()` することで必要なオブジェクトを取得することができます。

```ts:利用例
return responseBuilder.speak("Hello")
    .reprompt("World")
    .withSimpleCard("title", "content")
    .getResponse();
```

BuilderPatternが活用されるのは、条件分岐が頻発するときなどでしょう。

```ts:あんまり意味のないサンプル
const builder = handlerInput.responseBuilder;
builder.speak("Hello");

if (somethingIf()) {
    builder.withSimpleCard("title", "content");
}

return builder.getResponse();
```


## Resolver

パターンというよりレイヤーに近いんでしょうか。
Resolverという名前自体はよく見ますよね。
その名の通り、解決することを目的としており、利用者は **解決される側** を記述します。
Alexaでも同様で、 `canHandle()` がそれです。

```ts
canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
}
```

この `canHandle()` が生えたオブジェクトをSkillBuilderにaddすると、実行時にどのHandlerでhandleするかを解決できます。

これをResolverあり/なしで見ていきましょう。

```ts:Resolverあり
skillBuilder
  .addRequestHandlers(
    LaunchRequestHandler,
    HelloWorldIntentHandler,
    HelpIntentHandler,
    CancelAndStopIntentHandler,
    SessionEndedRequestHandler
  )
  .addErrorHandlers(ErrorHandler)
  .lambda();
```

```ts:Resolverなし
if (type === "LaunchRequest") {
    LaunchRequestHanlder.hanlde();
} else if (type === "IntentRequest") {
    if (name === "HelloWorldIntent") {
        HelloWorldIntentHandler.handle();
    } else if (type === "aaa") {
        /** サンプルすら書きたくないくらい冗長 */
    } else if () {
    } else if () {
    } else if () {
    } else if () {
    } else {
        ErrorHandler.hanlde();
    }
}
```

Intent増えるごとに `else if` 増えちゃいますよね。
サンプルすら書きたくないのに実務でやるのは絶対に嫌です。
SOLIDの原則のO === Open-Closed Principleを感じられる設計になっていて、拡張が容易ですね。

もっとやるなら[DecoratorPattern](https://ja.wikipedia.org/wiki/Decorator_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3)を採用し、SOLIDの原則に更に則りたいと思います。

DecoratorPatternを採用して[HelloWorldのサンプル](https://github.com/alexa/skill-sample-nodejs-hello-world/blob/master/lambda/custom/index.js)をこんな感じにするイメージです。


```ts
@RequestHanlder()
class LaunchRequestHandler implements RequestHandlerInterface {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
  }

  handle(handlerInput) {
    const speechText = 'Welcome to the Alexa Skills Kit, you can say hello!';

    return handlerInput.responseBuilder
      .speak(speechText)
      .reprompt(speechText)
      .withSimpleCard('Hello World', speechText)
      .getResponse();
  }
};
```

SOLIDの原則については、 @hidenorigoto さんの[続・SOLIDの原則ってどんなふうに使うの？](https://speakerdeck.com/hidenorigoto/sok-solidfalseyuan-ze-tutedonnahuunishi-ufalse-opunkurozudofalseyuan-ze-senpaifalsekodeingufalsetobian)というスライドがとても参考になるので是非読んでください。


## 終わりに

v1から大きく変更され、とても扱いやすいフレームワークになったask-sdkのv2の設計をちら見してみました。
「なんかよく見るぞ！」というコードおよびパターンを調べる際に、名前を知らないとどうも調べられない...ってことありますよね。
逆に名前を知ることで数珠つなぎに周辺の知識がついてくることが多いです。

Happy Coding!