# Introduction

Packhouse Introduction

![](https://4135423742-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LwD6MjHg3yMxWgKGDEI%2Fuploads%2FDZ8xDF5p2mTnJJBtRUhY%2Flogo.png?alt=media\&token=32be4b40-6083-4f9d-8b7c-f02f46a38c70)

![](https://img.shields.io/npm/v/packhouse.svg)[![travis-ci](https://travis-ci.org/KHC-ZhiHao/Packhouse.svg?branch=master)](https://travis-ci.org/KHC-ZhiHao/Packhouse)[![Coverage Status](https://coveralls.io/repos/github/KHC-ZhiHao/Packhouse/badge.svg?branch=master)](https://coveralls.io/github/KHC-ZhiHao/Packhouse?branch=master) [![Standard Code Style](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com/)[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/KHC-ZhiHao/Packhouse.svg?logo=lgtm\&logoWidth=18)](https://lgtm.com/projects/g/KHC-ZhiHao/Packhouse/context:javascript)[![](https://img.shields.io/github/stars/KHC-ZhiHao/Packhouse.svg?style=social)](https://github.com/KHC-ZhiHao/Packhouse)

**Packhouse**是一個基於函數式程式設計(Functional Programming)的程式設計模型，其擁有以下特性：

* [**追蹤**](/plugins/step)**呼叫上下文**
* **真正的**[**型態檢查**](/the-instance/group/mold)
* [**管理與分類**](/the-instance/group)**函式**
* **美麗的寫作規範**
* **建構**[**後端服務**](/application/api-service)**的能力**

{% hint style="info" %}
開始前可以閱讀[函數式編程指南](https://yucj.gitbooks.io/mostly-adequate-guide-traditional-chinese/content/)了解基本觀念。
{% endhint %}

## 為何採用**Packhouse？**

#### 1. 統一開發格式

使用**Packhouse**開發可以建構統一的Input/Output接口。

#### 2. 建構微服務

**Packhouse**的精神是建構微服務中的微服務，建立細微可控的函式能夠快速反應需求變更，且如果保持函數式編程的核心理念，便可以在專案破碎化的情況下複製模式到各個專案中。

#### 3. 當我們無法使用TypeScript

並不是所有的專案都能運行**TypeScript**，而**Packhouse**是原生的**JavaScript**，不需要經由任何編譯就能執行。

#### 4. Cloud Function

編寫**AWS Lambda**時將所有的邏輯編寫在一個檔案中難以應付頻繁的需求變更，物件導向開發在minify或編譯後難以除錯，雖然我們可以藉由單元測試來避免錯誤，但上線後會發生的事永遠比開發時離奇。

## 無伺服器架構 - Serverless

{% hint style="info" %}
你不需要逐步建立服務，可以直接參考[API Service](/application/api-service)章節。
{% endhint %}

無伺服器架構是**Packhouse**絕佳的運作平台，[**Mess**](/#shi-yong-an-li)的後端就是使用**Packhouse**與**Serverless Framework**建構整個**API Service**，我們推薦下列兩種**Serverless**框架：

#### [Serverless Framework](https://serverless.com/)

#### [AWS Serverless Application](https://docs.aws.amazon.com/zh_tw/serverlessrepo/latest/devguide/using-aws-sam.html)

## Install

```bash
$ npm i packhouse --save
```

## 運行環境

#### Node 8.x以上。

{% hint style="info" %}
Packhouse並沒有強制必須於哪個環境下運作，它甚至允許於瀏覽器執行，但我們不會在乎瀏覽器兼容性。
{% endhint %}

| <p><a href="http://godban.github.io/browsers-support-badges/"><img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge"></a></p><p>Edge</p> | [![Firefox](https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png)](http://godban.github.io/browsers-support-badges/)Firefox | [![Chrome](https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png)](http://godban.github.io/browsers-support-badges/)Chrome | [![Safari](https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png)](http://godban.github.io/browsers-support-badges/)Safari | <p><a href="http://godban.github.io/browsers-support-badges/"><img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari-ios/safari-ios_48x48.png" alt="iOS Safari"></a></p><p>iOS/Safari</p> | [![Samsung](https://raw.githubusercontent.com/alrra/browser-logos/master/src/samsung-internet/samsung-internet_48x48.png)](http://godban.github.io/browsers-support-badges/)Samsung | [![Opera](https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png)](http://godban.github.io/browsers-support-badges/)Opera |
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------: |
|                                                                                            support                                                                                           |                                                                              support                                                                              |                                                                            support                                                                            |                                                                            support                                                                            |                                                                                                        support                                                                                                       |                                                                                       support                                                                                       |                                                                          support                                                                          |

## First Function

以下是最低限度地執行程式：

```javascript
const Packhouse = require('packhouse')
const packhouse = new Packhouse()
const group = {
    tools: {
        sum: {
            handler: (self, v1, v2) => self.success(v1 + v2)
        }
    }
}

packhouse.addGroup('math', () => {
    return {
        data: group
    }
})

packhouse
    .tool('math/sum')
    .promise(10, 20)
    .then(result => console.log(result)) // 30
```

## 使用案例

![Mess的整個後端都由Packhouse建構](https://camo.githubusercontent.com/1ba21fad9b77e10efe2af4686f25cc41621eb02f/68747470733a2f2f6d6573732e6d6574616c73686565702e636f6d2f696d616765732f6c6f676f2e706e67)

[**MESS**](https://mess.metalsheep.com)**是為了通勤族精心設計的閱讀網站，你可以在任何等待時間使用任何裝置隨時閱讀國外媒體或文章，並享受精心設計的使用者介面與翻譯、語音服務，更棒的是：完全免費！**


# Versions

Packhouse Versions

## 1.x.x - 2018 \~ 2019

#### 一個純粹的開發與測驗版本，由於2.0.0以上版本是截然不同的架構，1.x的使用者請閱讀下列文件：

[Guide](https://khc-zhihao.github.io/MyBook/Packhouse/static/)

[API Document](https://khc-zhihao.github.io/Packhouse/old/document/document.html)

## 2.0.0 - 2020.02.02

#### 經過無數專案迭代整個架構後正式推出。

## 2.0.1 - 2020.05.17

#### Fixed

1. typescript mold提示錯誤

#### New

1. interceptError 攔截錯誤功能


# Group

Packhouse Group

## 架構

Packhouse所有的結構與方法都必須封裝在Group內，結構大概如下：

![](https://firebasestorage.googleapis.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LwD6MjHg3yMxWgKGDEI%2Fuploads%2FIrF1ZNXZLiicZuOrzoYW%2Ffile.png?alt=media)

## Property

[Mold](/the-instance/group/mold)： 參數型別，可視為Interface或Type的功能。

[Tools](/the-instance/group/tool)：方法(Function)的最小單元。

[Lines](/the-instance/group/line)：柯里化(Curry)的結構宣告。

```typescript
let group = {
    molds: 'object',
    tools: 'object',
    lines: 'object',
    mergers: 'object',
    install: 'function'
}
```

### Install

**Install**是初始化行為，也是Core和Group交換資料的地方，於初次引用時執行，以AWS S3為例：

```typescript
// s3.js
const group = {
    install: (group, options, packhouse) => {
        const AWS = require('aws-sdk')
        group.service = new AWS.S3({
            apiVersion: options.apiVersion
        })
    }
}

module.exports = group
```

#### Add Group

我們可以藉由`addGroup`方法加入Group，並傳遞Options，但我們不推薦這麼做，請參閱正規化方法[Main](/the-instance/main)。

```typescript
packhouse.addGroup('s3', () => {
    return {
        data: require('./s3'),
        options: {
            apiVersion: '2006-03-01'        
        }
    }
})
```

### Mergers

針對跨Group的設計的，當GroupA要使用GroupB的方法時不能直接使用對方，必須藉由merger與[Includ](broken://pages/-LwGVvGBa1zjgFvdW8LZ)的引用才行，此目的能協助綁定上下文以便建立[追蹤](/the-instance/event)行為。

```typescript
const group = {
    tools: {
        sum: {
            handler(self, v1, v2) {
                self.success(v1 + v2)
            }
        }
    }
}

const group2 = {
    mergers: {
        mySum: 'sum'
    },
    tools: {
        double: {
            install({ include }) {
                include('sum').tool('mySum/sum')
            },
            handler(self, value) {
                self.tool('sum')
                    .noGood(self.error)
                    .action(value, value, self.success)
            }
        }
    }
}

packhouse.addGroup('sum', () => {
    return {
        data: group
    }
})

packhouse.addGroup('double', () => {
    return {
        data: group2
    }
})

packhouse.tool('double/double').action(10, (error, result) => {
    console.log(result) // 20
})
```

## TypeScript

如果你使用TypeScript，可以使用我們提供的Declaration File：

```typescript
import { Group } from 'packhouse/types'

const group: Group = {}
```

## Lazy First

盡可能不要在外部宣告`require`，可以確保引入的效能、文件的生產與單元測試時Mock行為最佳化。

```typescript
// 這是個糟糕的例子 😢
const AWS = require('aws-sdk')
const group = {
    install: (group, options) => {
        group.service = new AWS.S3({
            apiVersion: options.apiVersion
        })
    }
}

module.exports = group
```

```typescript
// 這是個優良案例 🤣
const group = {
    install: (group, options) => {
        const AWS = require('aws-sdk')
        group.service = new AWS.S3({
            apiVersion: options.apiVersion
        })
    }
}

module.exports = group
```


# Mold

Packhouse Mold

## TypeScript

Mold的出現主要是因為JavaScript沒有Interface、Type等Struct的功能，這在運行過程時容易遇到非預期的錯誤。

TypeScript是一個解決方案，以下是使用時呈現的方式：

```javascript
// Basic function
function sum(v1: number, v2: number): number {
    return v1 + v2
}

// Interface based
interface Person {
    firstName: string
    lastName: string
}

function greeter(person: Person) {
    return "Hello, " + person.firstName + " " + person.lastName
}
```

{% hint style="info" %}
TypeScript的限制在編譯後就失去了功能，意味著今天在與客戶端交流時仍難以避免資料型態的意外。
{% endhint %}

## How To Use

Mold不能被外部所引用，最直接的使用方法是宣告在Tool的Request內。

```javascript
const tools = {
    demo: {
        request: ['string', 'number'],
        handler: self => self.success()
    }
}
```

在執行時能便會附加檢查功能：

```javascript
packhouse.tool('group/demo').action('123', '456', error => {
    console.log(error.message) // Parameter 1 not a number(456).
})
```

### Expression

Mold可以藉由`|`符號加入參數，協助開發者應付特殊狀況。

```javascript
const tools = {
    demo: {
        request: ['number|min:50'],
        handler: self => self.success()
    }
}
```

### Casting

能夠使用`self.casting`在[Handler](/the-instance/group/tool#handler)中做轉換。

```javascript
const tools = {
    demo: {
        handler(self, value) {
            try {
                let newValue = self.casting('number|min:50', value)
                self.success(newValue)
            } catch(e) {
                self.error(e)
            }
        }
    }
}
```

### Null

在Request中宣告`null`可以忽略驗證欄位。

```javascript
const tools = {
    demo: {
        request: [null, 'number'],
        handler: self => self.success()
    }
}
```

### Optional

在名稱後加入`?`符號可以使該參數為`null`或`undefined`的時候忽略驗證。

```javascript
const tools = {
    demo: {
        request: ['number?|min:50'],
        handler: self => self.success()
    }
}
```

## Custom Define Mold

Mold可以同時具有檢查與轉換的功能，呈現的方式如下：

{% hint style="info" %}
Mold不允許非同步運算。
{% endhint %}

```javascript
const molds = {
    int(value) {
        if (typeof value === 'number') {
            return Math.floor(value)
        } else {
            throw new Error('Value not a number.')
        }
    }
}
```

第二個參數為`context`，能夠做更進階的應用：

```javascript
const molds = {
    int(value, { index, utils, extras }) {
        if (extras.positive === true && value >= 0) {
            throw new Error(`Params ${index} must positive.`)
        }
        if (extras.min && Number(extras.min) > value) {
            throw new Error(`Params ${index} less than ${extras.min}.`)
        }
        if (typeof value === 'number') {
            return Math.floor(value)
        } else {
            throw new Error(`Params ${index} not a number.`)
        }
    }
}

const tool = {
    demo: {
        request: ['int|min:50|positive'],
        handler: (self, value) => self.success(value)
    }
}
```

## Verify Expression

基於[`utils.verify`](/the-instance/utils#verify)的格式，有更好的結構表達與文件生產。

```javascript
const molds = {
    person: {
        firstName: [true, ['string']],
        lastName: [true, ['string']]
    }
}

const tools = {
    demo: {
        request: ['person'],
        handler: (self, person) => self.success()
    }
}

let person: {
    firstName: 'green',
    lastName: null
}

// ... 省略註冊行為

packhouse.tool('group/demo').action(person, error => {
    // Parameter 0 verification error, Key(lastName) is required.
    console.log(error)
})
```

## Core Molds

不是每個Mold都要重新定義，系統本身提供了許多常用的型態：

#### type

使用[`utils.getType`](/the-instance/utils#gettype)作驗證。

* { string } is - 目標type，一定需要該參數

```javascript
['type|is:string']
```

#### number

參數必須為數字。

* **min** - 數字必須高於指定數字
* **max** - 數字必須低於指定數字
* **int** - 數字轉換成整數

```javascript
['number|min:0|max:50|int']
```

#### boolean

參數必須為`true`或`false`。

#### string

參數必須為字串。

* **is** - 指定字串內容，可以用`,`分割多個對象。

```javascript
['string|is: red, green, blue']
```

#### array

參數必須為陣列。

#### buffer

參數必須為`Buffer`。

#### object

參數必須為`Object`。

#### function

參數必須為`Function`。

#### date

參數必須為`new Date`支援的時間格式。

#### required

參數為必填。

> 如果你這樣寫required?，會無效。

## Create Self Core Mold

開發人員可以自定義系統Mold，這樣就能夠在所有場合使用自定義Mold。

{% hint style="info" %}
我們不建議個別自定義系統Mold，若有需要請參考[Merger](/the-instance/merger)章節來管理Mold。
{% endhint %}

{% hint style="info" %}
當系統Mold與Group Mold有相同名稱時，Group Mold優先。
{% endhint %}

```javascript
let packhouse = new Packhouse()
packhouse.addMold('int', value => parseInt(value))

const tools = {
    demo: {
        request: ['int'],
        handler: self => self.success()
    }
}
```


# Tool

Packhouse Tool

## Property

```typescript
const tool = {
    install: 'function',
    handler: 'function', // required
    request: 'array',
    response: 'string',
}
```

### Install

第一次呼叫時執行，只會執行一次。

{% hint style="info" %}
Install只允許同步執行，若執意使用非同步請求可以參閱[Loader](/the-instance/utils#loader)。
{% endhint %}

```javascript
const tool = {
    install({ packhouse, store, include, utils, group }) { ... }
}
```

#### Packhouse

實例化的Packhouse對象，主要在於運用[Plugin](/the-instance/plugin)。

#### Store

變數的容器，能在`handler`中被獲取。

```javascript
const tool = {
    install({ store }) {
        store.demo = 5
    },
    handler(self) {
        console.log(self.store.demo) // 5
    }
}
```

#### Include

引用其他的Tool必須經過`include`，該接口也能引用[其他Group](/the-instance/group#mergers)或[Line](/the-instance/group/line)。

```javascript
const group = {
   tools: {
      sum: {
         handler: (self, v1, v2) => self.success(v1 + v2)
      },
      double: {
         install({ include }) {
            // 參數一是引用名，第二個參數是tool name。
            include('sum').tool('sum')
         },
         handler(self, v1) {
            self.tool('sum')
                .action(v1, v1, (error, result) => {
                   self.success(result)
                })
         }
      }
   }
}
```

#### Group

與Group交換資料的管道，也意味著能與外部的參數進行溝通：

```javascript
const group = {
   install(group, options) {
      group.locale = options.locale
   },
   tools: {
      where: {
         install({ store, group }) {
            store.locale = group.locale
         },
         handler(self) {
            self.success(self.store.locale)
         }
      }
   }
}

packhouse.addGroup('locale', () => {
   return {
      data: group,
      options: {
         locale: '台灣'
      }
   }
})

packhouse.tool('locale/where').action((error, result) => {
   console.log(result) // '台灣'
})
```

#### Utils

可以使用Packhouse提供的工具組，詳情參照[Utils](/the-instance/utils)。

## Handler

Tool被呼叫時的主執行續。

{% hint style="info" %}
如果整個`handler`的過程是同步的，那`action`的執行是完全同步的，但建議把所有的行為視為非同步。
{% endhint %}

```javascript
const tool = {
    handler(self, ...args) {
        self.success()
    }
}
```

### Self

主要的流程控制單元，固定在第一個參數上，以下是它的Property：

#### success

回傳成功結果

#### error

回傳錯誤結果

#### casting

回傳與驗證參數，詳情請參閱[Mold Casting](/the-instance/group/mold#casting)。

#### line

引用`include`的[Line](/the-instance/group/line)。

#### tool

引用`include`的Tool。

#### assess

驗證第一個參數是否為null或undefined，若不是則回傳第一個參數，若成立則回傳第二個參數。

> assess可以接受一個Callback，當第二個參數成立時可觸發並回傳該方法的結果。

```javascript
const group = {
   tools: {
      sum: {
         request: ['number', 'number'],
         handler: (self, v1, v2) => self.success(v1 + v2)
      },
      doubleAndToInt: {
         install({ include }) {
            include('sum').tool('sum')
         },
         handler(self, v1) {
            self.tool('sum')
                .action(v1, v1, self.assess(result => parseInt(result)))
         }
      }
   }
}

// ... 省略註冊行為

packhouse.tool('math/doubleAndToInt').action(10.2, (error, result) => {
   console.log(result) // 20
})

packhouse.tool('math/doubleAndToInt').action('10.2', (error, result) => {
   console.log(error != null) // true
})
```

### Lazy First

在Cloud Function的型態下，我們只要專注在單一功能上，意味著每次執行只要針對目的`require`所需的模塊即可。

{% hint style="info" %}
並不建議將`require`的行為建立在`install`的行為內，除了可讀性差之外也可能造成`this`指向錯誤或影響單元測試等行為。
{% endhint %}

```javascript
// 這是個糟糕的例子😢
let moment = require('moment')
const badTool = {
    handler(self) {
        self.success(moment())
    }
}
```

```javascript
// 這是個優良案例 🤣
const tool = {
    handler(self) {
        let moment = require('moment')
        self.success(moment())
    }
}
```

### Async

Install必須是同步的，但Handler允許非同步運作。

```javascript
const tool = {
    handler: async self => setTimeout(self.success, 100)
}
```

## Request & Response

指定參數與回傳值的型態與驗證對象，比對方法詳情請參照[Mold](/the-instance/group/mold)。

```javascript
const tools = {
    sum: {
        request: ['number', 'number'],
        response: 'number',
        handler(self, v1, v2) {
            self.success(v1 + v2)
        }
    }
}
```


# Line

Packhouse Line

## Curry Function

Curry Function是個「將一個接受 n 個參數的 function，轉變成 n 個只接受一個參數的 function」的過程，JavaScript的Array就是經典的Curry Function表現，範例如下：

```javascript
let boys = ['boy', 'girl'].concat(['boy', 'girl']).filter(s => s === 'boy')
```

{% hint style="info" %}
但Packhouse的Curry實現完全不是這樣 😰。
{% endhint %}

## Property

Install、Request都是針對`input`的[Tool Property](/the-instance/group/tool#property)，對於執行Line的驗證。

而Response是針對`output`的驗證。

{% hint style="info" %}
Install的行為在每次呼叫時都會執行，對Line來說每一次都是新的開始。
{% endhint %}

```javascript
let line = {
    input: 'function',
    output: 'function',
    layout: 'tools',
    install: 'function',
    request: 'array',
    response: 'string'
}
```

## How To Use

為了擁有對付高度複雜性的功能，Line的定義並不輕鬆，Store是Line的主要共享狀態物件。

> 每次執行時Store都會是一個全新的對象，Tool則會永久保持最後設定的狀態。

{% hint style="info" %}
下列範例會看見只有在`output`時才真正回傳結果，而其他的動作代表`next`的行為。
{% endhint %}

```javascript
const group = {
    tools: {
        sum: {
            handler: (self, v1, v2) => self.success(v1, v2)
        }
    },
    lines: {
        math: {
            request: ['number'],
            response: 'number',
            install({ include }) {
                include('sum').tool('sum')
            },
            input(self, value) {
                self.store.value = value
                self.success()
            },
            output(self) {
                self.success(self.store.value)
            },
            layout: {
                add: {
                    request: ['number'],
                    handler(self, value) {
                        self.tool('sum')
                            .action((error, result) => {
                                self.store.value = result
                                self.success()
                            })
                    }
                },
                double: {
                    handler(self) {
                        self.store.value *= 2
                        self.success()
                    }
                }
            }
        }
    }
}

packhouse.addGroup('math', () => {
    return {
        data: group
    }
})
```

## Invoke

Line不存在noGood、always等[Pre-Invoke](broken://pages/-LwGVvGBa1zjgFvdW8LZ)行為，也必須丟入一個初始值觸發`input`，相對的並不需要在最後中Invoke行為中給予任何參數，詳情請見[Invoke](/the-instance/invoke)。

```javascript
packhouse
    .line('math/math')(10)
    .add(5)
    .add(10)
    .double()
    .action((error, result) => {
        console.log(result) // 50
    })
```


# Invoke

Packhouse Invoke

## Action

`action`最後的結果最由最後的參數(callback)呈現，第一個參數是錯誤，第二個參數為運算結果。

如果你的程式是同步的，那Action是完全同步的，但仍建議把全部的函式視為非同步。

{% hint style="info" %}
在某些狀況下第一個參數不會是`error`，詳情請參照[`noGood`](/the-instance/invoke#nogood)。
{% endhint %}

```javascript
packhouse.tool('math/sum').action(10, 20, (error, result) => {
    console.log(result) // 30
})
```

## Promise

如果使用Promise，則回傳Promise物件。

{% hint style="info" %}
在某些狀況下不會觸發`catch`，詳情請參照[`noGood`](/the-instance/invoke#nogood)。
{% endhint %}

```javascript
packhouse.tool('math/sum').promise(10, 20).then(result => {
    console.log(result) // 30
})
```

## Per Invoke

在執行Invoke前可以註冊一些行為。

### Pack

參數預處理，你可以在宣告之前先注入參數：

```javascript
packhouse
    .tool('math/sum')
    .pack(10)
    .action(10, (error, result) => {
        console.log(result) // 20
    })
```

#### In Include

在Install中引用`tool`時只要給予額外的參數也可以運行`pack`：

```javascript
const tool = {
    install({ include }) {
        include('sum').tool('sum', 10)
    }
}
```

在Include中引用`line`的後續參數也有`pack`的功能：

```javascript
const tool = {
    install({ include }) {
        include('math').line('math', 10)
    },
    handler(self) {
        self.line('math')()
            .action((error, result) => {
                console.log(result) // 10
            })
    }
}
```

### No Good

當錯誤處理時，由`noGood`接收。

{% hint style="info" %}
非監聽制，反覆宣告會覆蓋上一次的宣告。
{% endhint %}

#### Action

當宣告`noGood`後`action`的第一個參數會被移除。

```javascript
packhouse
    .tool('math/sum')
    .noGood(e => console.log(e))
    .action('1', '2', result => {
        // 這裡不工作
    })
```

#### Promise

當宣告`noGood`後`promise`不會被`reject`。

> 嚴格來說如果你使用的是Promise請使用`reject`而不是noGood

```javascript
packhouse
    .tool('math/sum')
    .noGood(e => console.log(e))
    .promise('1', '2')
    .then(() => {
        // 這裡會執行
    })
```

你可以運用第二個參數來強行宣告為`reject`。

```javascript
packhouse
    .tool('math/sum')
    .noGood(e => console.log(e), { reject: true })
    .promise('1', '2')
    .then(() => {
        // 這裡不會執行
    })
    .catch(() => {
        // 這裡會執行
    })
```

### Always

無論如何都會執行，事件發生在Action之後。

{% hint style="info" %}
非監聽制，反覆宣告會覆蓋上一次的宣告。
{% endhint %}

```javascript
packhouse
    .tool('math/sum')
    .always(({ result, success, context }) => {
        console.log(result) // 30
        console.log(success) // true
        console.log(context.id) // uuid, 每次執行都會配發一個新的id
        console.log(context.caller) // 如果該次呼叫是由另一個tool調用，可以得知是哪個id呼叫
    })
    .promise(10 , 20)
```

#### Promise

運行Promise時請使用`finally`而不是`alawys`。


# Utils

Packhouse Utils

&#x20;Utils是Packhouse內部的函數組，有許多功能都來自於Utils的建構，包括[Tool ID](/the-instance/utils#generateid)、[Mold Verify](/the-instance/utils#verify)等。

{% hint style="info" %}
所有的Utils是指向同一個對象，意味著你可以藉由Utils物件進行拓展，但我們不建議這麼做，良好的拓展方法請參照[Plugins](/the-instance/plugin)。
{% endhint %}

## How To Use

#### Core

```javascript
const Packhouse = require('packhouse')
const packhouse = new Packhouse()

packhosue.utils === Packhouse.utils // true
```

#### Tool

```javascript
const tool = {
    myTool: {
        install({ store, utils }) {
            store.id = utils.generateId()
            store.uuid = utils.generateId
        },
        handler(self) {
            console.log(self.store.id)
            console.log(self.store.uuid())
        }
    }
}
```

#### Mold

```javascript
const molds = {
    uuid(value, { utils }) {
        return utils.generateId()
    }
}
```

## Methods

#### **getType**

能比`typeof`獲取更多的型態，沒在下列名單內的揭回傳`typeof`的型態：

```javascript
packhouse.utils.getType([]) // array
packhouse.utils.getType(NaN) // NaN
packhouse.utils.getType(null) // empty
packhouse.utils.getType(undefined) // empty
packhouse.utils.getType(/test/) // regexp
packhouse.utils.getType(new Promise(() => {})) // promise
packhouse.utils.getType(Buffer.from('123')) // buffer
packhouse.utils.getType(new Error()) // error
```

#### **verify**

一個簡單的驗證方法，並回傳新的物件。

{% hint style="info" %}
`verify`與`mold`是分離的系統，無法使用`mold`表達示。
{% endhint %}

> `verify`驗證的`type`對象是多個型態，而支援的類型請參閱[getType](/the-instance/utils#gettype)。

```javascript
let options = {
    a: 5,
    b: []
}

let data = packhouse.utils.verify(options, {
    // [required, allow types, default value]
    a: [true, ['number']],
    b: [true, ['array']],
    c: [false, ['number'], 0]
})

console.log(data.a) // 5
console.log(data.c) // 0
```

#### **generateId**

仿`uuid`建立，回傳一個隨機`uuid`，但這並不是標準的`uuid`演算法。

```javascript
console.log(typeof packhouse.utils.generateId()) // string
```

但這不代表你無法使用[uuid](https://www.npmjs.com/package/uuid)演算法：

```javascript
let uuid = require('uuid/v4')
let packhouse = require('packhouse')

packhouse.utils.generateId = uuid
```

#### **peel**

針對物件深度取值，可以參考[可選鍊](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/%E5%8F%AF%E9%80%89%E9%93%BE)。

```javascript
let a = {
    b: {
        c: {
            d: 5
        }
    }
}
console.log(packhouse.utils.peel(a, 'b.c.d')) // 5
console.log(packhouse.utils.peel(a, 'b.c.e')) // undefined
```

#### loader

返回一個Promise，第一次執行後會執行程序，但後續的呼叫皆回傳第一次呼叫的結果。

`loader`的存在目的很簡單，在Tool Install的週期時，有時會存在需非同步請求才能獲得的資料，但該週期又不支援非同步運算，因此你可以藉由`loader`進行包裹來達成初步的Cache。

> 更進階的快取應用可以參照[Order](/plugins/order)。

```javascript
let loader = packhouse.utils.loader((resolve, reject) => {
    setTimeout(() => {
        resolve('hello')
    }, 1000)
})

loader().then(result => {
    // 等待一秒才執行
    console.log(result) // hello
    loader().then(result => {
        // 不需等待
        console.log(result) // hello
    })
})
```


# Event

Packhouse Event

Packhouse提供了簡單的事件系統，主要提供於追蹤呼叫行為。

{% hint style="info" %}
除非特殊情況，你不需要自己處理事件追蹤，使用[Step](/plugins/step)，可以輕鬆追蹤整個運行過程。
{% endhint %}

## Add Listener

#### Use

第一次使用Tool時觸發：

{% hint style="info" %}
嚴格來說Line是一連串的Tool執行，在呼叫Line的事件中你會發現Name會以Namespace的模式顯示。
{% endhint %}

```javascript
packhouse.on('use', (event, { type, name, group }) => {
    console.log(type) // tool
    console.log(name) // sum
    console.log(group.sign) // null *this is merger sign
    console.log(group.name) // math
})

packhouse.tool('math/sum').action(...)
```

#### Run

每次執行Tool時會被觸發：

```javascript
packhouse.on('run', (event, { id, caller, detail }) => {
    let { name, args, mode, request, response, group } = detail
    console.log(id) // uuid, 每次執行都會配發一個新的id
    console.log(caller) // 如果該次呼叫是由另一個tool調用，可以得知是哪個id呼叫
    console.log(name) // sum
    console.log(args) // [10, 20]
    console.log(mode) // action
    console.log(request) // "[\"number\", \"number\"]"
    console.log(response) // number
    console.log(group.sign) // null *this is merger sign
    console.log(group.name) // math
})

packhouse.tool('math/sum').action(10, 20, () => {})
```

### Done

運行結束時觸發：

```javascript
packhouse.on('done', (event, { id, caller, detail }) => {
    let { result, success } = detail
    console.log(id) // 與執行的id相同
    console.log(caller) // 如果該次呼叫是由另一個tool調用，可以得知是哪個id呼叫
    console.log(success) // true
    console.log(result) // 30
})

packhouse.tool('math/sum').action(10, 20, () => {})
```

## Remove Listener

每次監聽事件都會回傳一個監聽Id，因此可以藉由該Id指定移除事件：

```javascript
let id = packhouse.on('run', () => {})

packhouse.off('run', id)
```

也可以利用第一個`event`參數來關閉事件：

```javascript
packhouse.on('run', event => event.off())
```


# Merger

Packhouse Merger

## Property

[Molds](/the-instance/group/mold)： 參數型別，可視為Interface或Type的功能。

Groups：[Group](/the-instance/group)群組，應用方法請至[How To Use](/the-instance/merger#how-to-use)查看。

```javascript
let merger = {
    molds: 'molds',
    groups: 'groups'
}
```

## TypeScript

如果你使用TypeScript，可以使用我們提供的Declaration File。

```javascript
import { Merger } from 'packhouse/types'

let merger: Merger = {}
```

## How To Use

我們可以藉由`merger`方法加入Merger，並傳遞Options，但我們不推薦這麼做，請參閱正規化方法[Main](/the-instance/main)。

{% hint style="info" %}
Merger會以[`namespace@`](/the-instance/merger#sign)的Sign建立Mold與Group。
{% endhint %}

### Molds

Merger的Mold會被註冊進Core Mold中，各地都能引用。

```javascript
const group = {
    tools: {
        sum: {
            request: ['myMerger@int', 'myMerger@int'],
            handler(self, v1, v2) {
                self.success(v1 + v2)
            }
        }
    }
}

const merger = {
    molds: {
        int(value) {
            return Math.floor(value)
        }
    }
}

packhouse.merger('myMerger', merger)
packhouse.addGroup('math', group)

packhouse.tool('math/sum').action(1.1, 2.5, (err, result) => {
    console.log(result) // 3
})
```

### Groups

```javascript
const group = {
    tools: {
        sum: {
            handler(self, v1, v2) {
                self.success(v1 + v2)
            }
        }
    }
}

const merger = {
    groups: {
        math(options) {
            console.log(options.PI) // 3.14159...
            return {
                data: group
            }
        }
    }
}

packhouse.merger('myMerger', merger, {
    PI: Math.PI
})

packhouse.tool('myMerger@math/sum').action(...)
```

## Module

Merger的另一個特點是內部引用，當Merger互相引用對方時不需要添加`sign`。

```javascript
let group1 = {
    tools: {
        sum: {
            handler(self, v1, v2) {
                self.success(v1 + v2)
            }
        }
    }
}

let group2 = {
    mergers: {
        mySumGroup: 'sumGroup'
    },
    tools: {
        double: {
            install({ include }) {
                include('sum').tool('mySumGroup/sum')
            },
            handler(self, v1) {
                self.tool('sum').action(v1, v1, (e, r) => {
                    self.success(r)
                })
            }
        }
    }
}

let merger = {
    groups: {
        sumGroup() {
            return {
                data: group1
            }
        },
        doubleGroup() {
            return {
                data: group2
            }
        }
    }
}

packhouse.merger('myMerger', merger)

packhouse.tool('myMerger@doubleGroup/double').action(10, (e, r) => {
    console.log(r) // 20
})
```


# Plugin

Packhouse Plugin

Packhouse的Plugin只能存在於實體化的對象，沒有全局註冊的行為。

> [Order](/plugins/order)是我們提供的套件> ，也是一個簡單的開發範例。

{% hint style="info" %}
我們可以藉由我們不推薦你直接使用`plugin`，請參閱正規化方法[Main](/the-instance/main)。
{% endhint %}

```javascript
const Order = require('packhouse/plugins/Order')
const Packhouse = require('packhouse')
const packhouse = new Packhouse()

packhouse.plugin(Order)

console.log(packhouse.order()) // Order
```

## Develop

Plugin是一個`class`，會在`constructor`的第一個參數中接收到實體化的Packhouse對象。

```javascript
class MyPlugin {
    constructor(packhouse) {
        packhouse.hello = function() {
            console.log(`hello world.`)
        }
    }
}

const Packhouse = require('packhouse')
const packhouse = new Packhouse()

packhouse.plugin(MyPlugin)
packhouse.hello() // hello world.
```

## Repeat Registration Of Plugins

當Packhouse偵測到相同的Class對象，則不會有任何反應。

```javascript
packhouse.plugin(Order)
// 以下行為不會觸發任何事
packhouse.plugin(Order)
```

這意味著當你基於外部開發時引入某些Plugin時能有更多元的註冊行為：

```javascript
const group = {
    install(group, options, packhouse) {
        packhouse.plugin(require('packhouse/plugins/Order'))
    }
}
```


# Main

Packhouse Main

Main是一個靜態方法，主要是基於管理的語法糖，使用方法如下：

{% hint style="info" %}
以下的範例可能有點抽象，你可以直接參考[API Service](/application/api-service)來了解其運作概念。
{% endhint %}

```typescript
const Order = require('packhouse/plugins/order')
const Packhouse = require('packhouse')

let main = Packhouse.Main((packhouse, event) => {
    // 可以傳遞參數
    console.log(event.hello) // world
    // 可以定義事件
    packhouse.on('run', () => { ... })
    // 回傳基本資料
    return {
        plugins: [
            Order
        ],
        groups: {
            'math': () => {
                return {
                    data: require('./math'),
                    options: {}
                }
            }
        },
        mergers: {
            'myMerger': () => {
                return {
                    data: require('./myMerger'),
                    options: {}
                }
            }
        }
    }
})

let packhouse = main({
    hello: 'world'
})

packhouse.tool('math/sum').action(...)
```

## TypeScript

如果你使用TypeScript，可以使用我們提供的Declaration File：

```typescript
import { Main } from 'packhouse/types'

const Main: Main = Packhouse.Main
const main = Main(() => ({})
const packhouse = main()
```


# Intercept Error

Intercept Error

Intercept Error可以為你的服務器建立專屬的錯誤格式，協助你的程式能夠分別錯誤來源。

```javascript
let packhouse = new Packhouse()
let group = {
    tools: {
        demo: {
            handler(self) {
                self.error('error')
            }
        }
    }
}

packhouse.addGroup('demo', () => {
    return {
        data: group 
    }
})

packhouse.interceptError(error => {
    return {
        error,
        isPackhouse: true
    }
})

packhouse.tool('demo/demo').action(error => {
    console.log(error.error) // error
    console.log(error.isPackhouse) // true
})

```


# Step

Packhouse Step

### How To Use

Packhouse本體只是個函數設計模型，而Step負責建構`middleware`與流程設計使得Packhouse具有實際建構服務的能力。

Step是一個[Plugin](/the-instance/plugin)，使用前必須先引用：

```javascript
let Step = require('packhouse/plugins/Step')
let Packhouse = require('packhouse')
let packhouse = new Packhouse()

packhouse.plugin(Step)
```

{% hint style="info" %}
與其說本頁面是教學更像是說明文件，實際的應用方式可以直接參照[API Service](/application/api-service)。
{% endhint %}

## Flow

整個flow是經由下列控制：

* `next`下一步。
* `exit`跳出至`output`，可以攜帶一個參數，會傳遞至`output`的`message`。
* `fail`跳出至`output`，但`fail`為`true`，可以攜帶一個參數，會傳遞至`output`的`message`。

```javascript
let step = packhouse.step({
    timeout: 25000, // ms
    create: function(self, { exit, fail }) {
        self.result = 0
    },
    middle: function(self, { exit, fail }) {
        if (typeof self.result !== 'number') {
            exit()
        }
    },
    // required
    output(self, { timeout, history, fail, message }, done, error) {
        done(self.result)
    },
    // required
    template: [
        function add(self, next, { exit, fail }) {
            self.result = 10
            next()
        },
        function double(self, next) {
            self.result *= 2
            next()
        }
    ]
})

step.then((result) => {
    console.log(result) // 20
})
```

上面的程式的運作如下：

<div align="left"><img src="https://4135423742-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LwD6MjHg3yMxWgKGDEI%2F-LwY_lq11kEvPv_LGQoZ%2F-LwYsJGuPwnxtz_0NXex%2FUntitled%20Diagram%20(4).png?alt=media&amp;token=9ede6fd5-7b1d-4f82-bec1-ee8e441df46d" alt=""></div>

### Create

第一次執行Step時觸發的方法，必須是同步的函數，可以執行跳出事件。

### Middle

`create -> template -> output` 行為之間都會出發`middle`，可以作為跳出判定層，必須是同步函數。

### Output

接收所有運算結果的地方，為非同步函數，最後兩個參數為`done`與`error`，決定整個Step為`reslove`還是`reject`。

```javascript
const output = (self, { timeout, history, fail, message }, done, error) => {
    done('success!')
}
```

### Timeout

指定運行的毫秒數，如果省略則無限制，當運行時間超過時會無條件呼叫`output`，且`timeout`為`true`。

{% hint style="info" %}
由於`output`有可能需要處裡請求事件，Timeout的設計並不包含該流程的時間，在AWS APIGateway開發的過程需要注意只能運行30秒的限制。
{% endhint %}

```javascript
const output = (self, { timeout}, done, error) => {
    console.log(timeout) // true or false
}
```

### Fail

當呼叫`output`的對象是`fail`時為`true`。

### Message

當`fail`或`exit`有攜帶參數時會帶入的值。

### Template

運作的過程，與[`async waterfall`](https://caolan.github.io/async/v3/docs.html#waterfall)雷同，會隨著陣列的順序依序宣告，執行到最末端後執行`output`，也允許執行跳出整個流程。

### History

記錄整個執行過程的物件，可輸出`JSON Log`。

```javascript
{
    profile: 'object',
    template: 'template log',
    isDone: 'function',
    toJSON: 'function'
}
```

#### Profile

| Key        | Value           |
| ---------- | --------------- |
| fail       | 是否為Fail         |
| message    | 同output message |
| timeout    | 是否為timeout      |
| startTime  | 起始時間            |
| finishTime | 結束時間            |
| totalTime  | 總運行時間(ms)       |

#### Template

整體運作的細部資料，除非不得已，否則我們建議使用[ToJSON](/plugins/step#to-json)來檢視整個過程。

#### Is Done

如果有對Template的函數進行命名，可以使用`isDone`來驗證是否有運行該函數。

```javascript
const step = {
    template: [
        function myFunction(self, next, { exit }) {
            exit()
        },
        function myFunction2(self, next) {
            next()
        }
    ],
    output(self, { history }, done) {
        console.log(history.isDone('myFunction')) // true
        console.log(history.isDone('myFunction2')) // false
        done()
    }
}
```

#### To JSON

將細部運作資料轉換成JSON可存放資料。

{% hint style="info" %}
輸出的資料將是個大型的JSON文件，尤其是在參數與回傳值非常龐大的情況下，請自行評估何時運作`toJSON`來確保是否佔用太多資源。
{% endhint %}

```javascript
let Step = require('packhouse/plugins/Step')
let Packhouse = require('packhouse')
let packhouse = new Packhouse()

packhouse.plugin(Step)
packhouse.addGroup('math', () => {
    return {
        data: {
            tools: {
                sum: {
                    handler(self, v1, v2) {
                        self.success(v1 + v2)
                    }
                }
            }
        }
    }
})

packhouse.step({
    template: [
        function sum(self, done) {
            packhouse.tool('math/sum').action(10, 20, () => {
                done()
            })
        }
    ],
    output(self, { history }, done) {
        console.log(history.toJSON({
            hello: 'world'
        }))
        done()
    }
})

// 以下是輸出的JSON

{
    "profile": {
        "fail": false,
        "timeout": false,
        "startTime": 1577071246765,
        "finishTime": 1577071246769,
        "totalTime": 4
    },
    "metadata": {
        hello: 'world'
    },
    "template": [
        {
            "name": "sum",
            "logs": {
                "5f0e1c0e-e537-42f3-ab3b-cc6689aada5e": {
                    "logs": {},
                    "startTime": 1577071246768,
                    "name": "sum",
                    "args": [
                        10,
                        20
                    ],
                    "caller": null,
                    "mode": "action",
                    "request": "[]",
                    "response": null,
                    "group": {
                        "name": "math",
                        "sign": ""
                    },
                    "success": true,
                    "result": 30,
                    "finishTime": 1577071246768,
                    "totalTime": 0
                }
            },
            "startTime": 1577071246766,
            "finishTime": 1577071246768,
            "totalTime": 2
        }
    ]
}
```

我們提供了簡易的JSON Template檢視工具來協助你開發：

[History檢視工具](https://khc-zhihao.github.io/Packhouse/tools/step-view/)


# Order

Packhouse Order

在函數式編程中，通常會將多個請求發送到相同的條件，因為即使鍵值相同，我們也必須將每個請求視為新服務，這意味著不必要的性能浪費。

![這是我們常見的請求模式](https://firebasestorage.googleapis.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LwD6MjHg3yMxWgKGDEI%2Fuploads%2FcRoFoFPkyIyQOck9pI6p%2Ffile.png?alt=media)

因此，當請求的條件相同，我們可以視為回傳結果相同，因此在第一次請求回傳之前所有相同鍵值的請求都會等待第一次回傳的結果。

![當理論上請求的結果相同時，只需要進行一次請求](https://firebasestorage.googleapis.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LwD6MjHg3yMxWgKGDEI%2Fuploads%2FFQEgwWWKQYRUgHhugTAe%2Ffile.png?alt=media)

## How To Use

Order是一個[Plugin](/the-instance/plugin)，使用前必須先引用：

```javascript
let Order = require('packhouse/plugins/Order')
let Packhouse = require('packhouse')
let packhouse = new Packhouse()

packhouse.plugin(Order)
```

以下是Order是如何根據Key獲取快取結果：

```javascript
let group = {
    tools: {
        order: {
            install({ store, packhouse }) {
                store.order = packhouse.order()
            },
            handler(self, key) {
                self.store
                    .order
                    .use(key, self, (error, success) => {
                        setTimeout(() => {
                            success(Date.now())
                        }, 1000)
                    })
            }
        }
    }
}

packhouse.addGroup('demo', () => ({ data: group }))

Promise.all([
    packhouse.tool('demo/order').promise('myKey'),
    packhouse.tool('demo/order').promise('myKey')
]).then(([d1, d2]) => {
    console.log(d1 === d2) // true
})

Promise.all([
    packhouse.tool('demo/order').promise('myKey2'),
    packhouse.tool('demo/order').promise('myKey3')
]).then(([d1, d2]) => {
    console.log(d1 === d2) // flase
})

```

## Use

Use是Order主要的使用方法：

{% hint style="info" %}
Use不會長時間Cache結果，當結果回傳後就會清除該Key值的Cache。
{% endhint %}

```javascript
let order = packhouse.order()
order.use(key, { success, error }, handler)
```

除了使用Use方法外，可以個別的調適Order行為：

{% hint style="info" %}
我們不贅述內部細節，因為這不是Order建議的操作模式。
{% endhint %}

```javascript
let order = packhouse.order()
// 這是use呼叫的呼叫過程
order
    .getOrCreate(key)
    .buffer({
        success() { ... },
        error() { ... }
    })
    .finish(cache => cache.clear())
    .action((error, success) => {
        success( ... )
    })
```


# Unit Test

Packhouse Test

能針對每個環節進行測試，這是函數式程式設計的優點之一，而Packhouse提供了一種簡單的Mock方法能應付所有細節：

{% hint style="info" %}
Test是一個[Plugin](/the-instance/plugin)，使用前必須先引用：
{% endhint %}

```javascript
let Test = require('packhouse/plugins/Test')
let Packhouse = require('Packhouse')
let packhouse = new Packhouse()

packhouse.plugin(Test)

let group = {
    tools: {
        sum: {
            handler(self, v1, v2) {
                self.success(v1 + v2)
            }
        }
    }
}

packhouse.addGroup('math', () => ({ data: group }))

// mock options
packhouse.test.mock('tool', 'math/sum', options => {
    options.handler = self => self.success(50)
})

packhouse.tool('math/sum').action(10, 20, (error, result) => {
    console.log(result) // 50
})

// restore
packhouse.test.restore('tool', 'math/sum')

packhouse.tool('math/sum').action(10, 20, (error, result) => {
    console.log(result) // 30
})
```

### Restore All

將所有Mock物件回到原樣：

```javascript
packhouse.test.restoreAll()
```

## 使用mocha與chai

[mocha](https://mochajs.org/)與[chai](https://www.chaijs.com/)分別為單元測試庫與斷言庫，這只是一個泛用測試組合，以下範例說明如何應用Packhouse進行測試：

```javascript
let { expect } = require('chai')
let Test = require('packhouse/plugins/Test')
let Packhouse = require('Packhouse')
let packhouse = new Packhouse()

packhouse.plugin(Test)

let group = {
    tools: {
        sum: {
            handler(self, v1, v2) {
                self.success(v1 + v2)
            }
        }
    }
}

describe('#Test', function() {
    this.timeout(0)
    before(function() {
        packhouse.test.mock('tool', 'math/sum', options => {
            options.handler = self => self.success(50)
        })
    })

    after(function() {
        packhouse.test.restore('tool', 'math/sum')
    })

    it('test', function(done) {
        packhouse.tool('math/sum').action(10, 10, (error, result) => {
            expect(result).to.equal(50)
        })
    })
})
```


# API Service

Packhouse API Service

### 在開始前必須了解...

* [AWS](https://aws.amazon.com/tw/)的基本知識
* [Serverless](https://serverless.com/)的架構與原理

## Install

{% hint style="info" %}
開始前你必須安裝[AWS-CLI](https://aws.amazon.com/tw/cli/)並擁有個人的Secret Access Key允許Serverless可以建立服務。
{% endhint %}

#### 安裝CLI工具集

```bash
npm install serverless packhouse-cli mocha -g
```

#### 建立服務檔案

```javascript
packhouse generate-api-service
```

{% hint style="danger" %}
你必須手動添加你的`IAM-ROLE`使你的Lambda有能力使用其他服務，可於`serverless.yml`中找到以下欄位：
{% endhint %}

```yaml
provider:
  name: aws
  runtime: nodejs12.x
  ### 這裡
  role: YOUR-IAM-ROLE
  ###
  stage: v1
  region: us-east-1
  timeout: 30
  memorySize: 256
```

#### 初始化及部署服務

{% hint style="info" %}
建立的服務預設Region在`us-east-1。`
{% endhint %}

```javascript
cd service
npm install
npm run deploy
```

## Architecture

這是我們建立Packhouse前使用的架構：

![參考自php laravel](https://4135423742-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LwD6MjHg3yMxWgKGDEI%2F-Lzjd5y8iCn2FsQiuvk_%2F-LzjtpjB16EJqx3F9J6r%2Fimage.png?alt=media\&token=7185ef44-ce62-4135-88fd-e37f44cab94d)

由於是建立在Cloud Service的服務，因此Database的操作大多來自SDK，而不是SQL，所以讀寫資料庫的工作交由Repository：

![Packhouse的架構跟Model2相差不遠](https://4135423742-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LwD6MjHg3yMxWgKGDEI%2F-Lzjd5y8iCn2FsQiuvk_%2F-Lzjv096DdPzaskiKmmH%2Fimage.png?alt=media\&token=50bf46ad-2aa3-44ba-9235-20404f1057c0)

## Invoke

當你部屬好你的第一個服務後，就會看見以下這段Log：

```javascript
endpoints:
  GET - https://xxxxxx.execute-api.us-east-1.amazonaws.com/v1/hello
```

開啟你的瀏覽器並貼上這段網址：

```javascript
https://xxxxxx.execute-api.us-east-1.amazonaws.com/v1/hello?message=helloworld
```

即可看到你的第一個Response：

<div align="left"><img src="https://4135423742-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LwD6MjHg3yMxWgKGDEI%2F-Lzjd5y8iCn2FsQiuvk_%2F-LzkMAS-HU406iB2rDHA%2Fimage.png?alt=media&amp;token=9cdbc901-b892-477d-922d-dd689f20cb8b" alt=""></div>

也可以在**Dynamodb**中看到你的Message Log：

<div align="left"><img src="https://4135423742-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LwD6MjHg3yMxWgKGDEI%2F-Lzjd5y8iCn2FsQiuvk_%2F-LzkMLZy2E12MtMQiOCE%2Fimage.png?alt=media&amp;token=7c7bba04-e5ed-4f36-b310-334634d8c963" alt=""></div>

{% hint style="danger" %}
為了避免被收取額外費用，你可以使用`npm run remove`指令關閉整個服務。
{% endhint %}

## AWS Exception

AWS SDK的所有方法雖然都有提供`promise`接口，但它的`promise`有一個糟糕的問題，在有一定複雜的呼叫過程時如果有程式碼報錯，`promise`會捕捉到`error`卻不會觸發`catch()`。

```javascript
// 這是個糟糕的例子 😢
let AWS = require('aws-sdk')
let client = new AWS.DynamoDB.DocumentClient()
let group = {
    tools: {
        getUser: {
            handler(self, name) {
                let parmas = {
                    TableName: 'users',
                    Key: {
                        name
                    }
                }
                // 避免如下宣告
                client.get(params).promise().then(self.success).catch(self.error)
            }
        }
    }
}
```

```javascript
// 這是個優良案例 🤣
let AWS = require('aws-sdk')
let client = new AWS.DynamoDB.DocumentClient()
let group = {
    tools: {
        getUser: {
            handler(self, name) {
                let parmas = {
                    TableName: 'users',
                    Key: {
                        name
                    }
                }
                client.get(params, self.assess())
            }
        }
    }
}
```


