前一篇文章 介紹如何使用工廠方法。本篇介紹另一種技巧能切開商業邏輯和資料存取的程式碼。
以之前電商平台的物流系統為例,假如此專案是外包的需求,A客戶指定用MySQL;而B客戶習慣PostgreSQL。在實作部分一開始為:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const db = require('./db'); // 宣告超商取貨 class ConvenienceStoreDelivery : Delivery { constructor(shipAddress) { // 略 } get shippingRate() { return (async() => db.query('SELECT rate FROM tbl_shipment_info WHERE type = convenience_store'); )(); } get freeShipmentThreshold() { return (async() => db.query('SELECT free_shipment_threshold FROM tbl_shipment_info WHERE type = convenience_store'); )(); } }
在上例中,我們依賴 db 這個檔案建立連線和設定。而這也帶來缺點: 寫測試時必須依賴實體的 database、不同的實作要使用另一個連線時要給額外的參數。
此時可以應用 dependency injection。在 Node.js 裡有三種方式可以達成
constructor
setter
bind
本文使用 constructor 當範例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 class ConvenienceStoreDelivery : Delivery { constructor(shipAddress, db) { this.db = db; this.shipAddress = shipAddress; } get shippingRate() { return (async() => this.db.query('SELECT rate FROM tbl_shipment_info WHERE type = convenience_store'); )(); } get freeShipmentThreshold() { return (async() => this.db.query('SELECT free_shipment_threshold FROM tbl_shipment_info WHERE type = convenience_store'); )(); } } // database.js class Database { constructor(config) { this._config = config; } connect() { // connect db } query() { // 略 } } // config.js const config = { MYSQL_CONFIG: { server: process.env.DB_SERVER, database: process.env.DB_NAME, user: process.env.DB_USER, password: process.env.DB_PW }, POSTGRES_CONFIG: { // 略 } } // 用戶端 const config = require('./config'); const Database = require('./database'); const db = new Database(config.MYSQL_CONFIG); db.connect(); const factory = new ConvenienceStoreDeliveryFactory(); const convenienceStoreDelivery = factory.createDeliveryService(shipAddress, db);
這樣就能把 database 和實作隔開,如果專案要換其它的 db,只要改設定值 (或用環境變數) 即可。 應用此技巧能把二個不必要的耦合分開來,讓之後的修改更有彈性,nest.js 也採用這種方式設計框架。
ref: Dependency Injection in nodejs