---
# System prepended metadata

title: Koi Linebot 開發心得

---

# Koi Linebot 開發心得
Source: https://github.com/SharpKoi/Koi-Linebot
## 描述
## Integrations
- Flask
- Gunicorn
- SQLAlchemy(flask_sqlalchemy)
- Postgresql database
- Heroku Cloud
- Line Message API
## 遇到的困難
:::info
- Flask app 及 SQLAlchemy之間的互動導致設計架構時常常需要刻意避免circular import，寫的綁受綁腳。
  **解決方案:** 
  需使用特定的架構來避免問題發生，最通用的思維是將成員按照階級排序，較常被使用的成員擁有較低的階級(需伺候別人的僕人)，較少被用到且須用到其他成員的成員擁有較高階級(需要別人伺候的貴族)，再將成員依據階級高低放在不同的py module裡來區分階級。
  高階級的py module可以import較低階級的py module，但低階級的py module不可import較高階級的py module。
:::
:::success
- gunicorn app與flask app的整合使得在app外的邏輯難以實現。
  **解決方案:**
  自定義一個class繼承`gunicorn.base.app.BaseApplication`，將初始化的邏輯放入`__init__`裡(例如創建目錄路徑、初始化需要用到的物件...etc)，將執行的邏輯放到`run`裡(例如thread, schedules, ...etc)。
  接著初始化這個類，用`gunicorn <your module>:<app name>`指令執行或是直接在程式碼中呼叫`run()`，再以`python <your_module>.py`執行程式檔。
  **Example:**
  ```python
  class GunicornApp(BaseApplication, ABC):
      def __init__(self, application, options=None):
        self.options = options or {}
        self.application = application

        # create some needed paths
        create_dirs()

        # register something needed
        register_handler(NotificationHandler())

        super().__init__()

      def load_config(self):
        cfg = {key: value for key, value in self.options.items() if key in self.cfg.settings and value is not None}
        for key, value in cfg.items():
            self.cfg.set(key.lower(), value)

      def load(self):
        return self.application

      def run(self):
        # run your threads when run the app
        your_thread.start()
        super().run()
        
  wsgi_app = GunicornApp(app)
  if __name__ == "__main__":
      wsgi_app.run()
  ```
:::
:::info
- gunicorn workers fork the same database connection of the master process. Which causes "SSL error: decryption failed or bad record mac"
  **解決方案:**
  在初始化app後呼叫`engine.dispose()`來關閉connection pool裡的所有connections，等待新的query提交才會建立新的連線，這樣就能在workers forking後建立新連線，避免workers forking造成的問題。
  **Example:**
  ```python
  wsgi_app = GunicornApp(app)
  db.engine.dispose() # dispose after forking
  if __name__ == "__main__":
      wsgi_app.run()
  ```
:::
:::success
- 本地主機、雲端主機、資料庫時間欄位 之間使用的時區不同，導致日期時間的處理變得複雜
  **解決方法:**
  所有時間上的處理都固定使用UTC時區，只有顯示給使用者看時才使用使用者所在的時區。
:::
