# 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時區,只有顯示給使用者看時才使用使用者所在的時區。 :::