資料庫 Adapters 就像 IKEA 家具一樣提供各種不同的組裝套件,你可以根據不同的需求來選擇適合的 Adapter 。 Moleculer 框架有屬於自己的資料庫 Adapters,你可以使用它來將資料儲存於資料庫。
Moleculer 遵循一個服務一個資料庫的模式。想了解更多可以參閱此設計模式文章[2] 。關於資料庫 Adapters 的常見問題可以參閱 FAQ[3] 。
MongoDB
、PostgreSQL
、SQLite
、MySQL
、MSSQL
Moleculer 使用 NeDB[4] 作為預設的 Adapter ,你可以快速使用它來配置與測試原型系統。
注意,NeDB 只能用在測試或原型開發環境。生產環境請換到
Mongo
、Mongoose
或Sequelize
等 adapter ,這些方法都具備通用的設定、 Action 與方法。
npm install moleculer-db --save
"use strict";
const { ServiceBroker } = require("moleculer");
const DbService = require("moleculer-db");
const broker = new ServiceBroker();
// 建立一個 users 服務
broker.createService({
name: "users",
// 將 DB 函數混合到 'users' 服務
mixins: [DbService],
settings: {
fields: ["_id", "username", "name"],
entityValidator: {
username: "string"
}
},
afterConnected() {
// 建立一些初始資料函數 `this.create`
}
});
broker.start()
// 建立新使用者
.then(() => broker.call("users.create", {
_id: 1,
username: "john",
name: "John Doe",
status: 1
}))
// 取得所有 user
.then(() => broker.call("users.find").then(console.log))
// 使用分頁取得 user
.then(() => broker.call("users.list", { page: 2, pageSize: 10 }).then(console.log))
// 取得一筆 user
.then(() => broker.call("users.get", { id: 1 }).then(console.log))
// 更新一筆 user
.then(() => broker.call("users.update", { id: 1, name: "Jane Doe" }).then(console.log))
// 刪除一筆 user
.then(() => broker.call("users.remove", { id: 1 }).then(console.log));
更多範例請參閱:
https://github.com/moleculerjs/moleculer-db/tree/master/packages/moleculer-db/examples
所有的資料庫 adapters 都能使用通用的設定:
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
idField |
<String> | "_id" |
欄位名稱 |
fields |
<String[]> | null |
欄位過濾清單。如果設為 null 或 undefined 則不過濾。 |
populates |
<Array> | null |
填充綱目 |
pageSize |
<Number> | 10 |
list Action 的分頁大小 |
maxPageSize |
<Number> | 100 |
list Action 的最大分頁大小 |
maxLimit |
<Number> | -1 |
find Action 的最大限制資料數。設為 -1 為不限制。 |
entityValidator |
<Object> | <function> | null |
create 及 insert Action 用來驗證傳入實體的驗證器綱目或函數。 |
idField
不適用於 Sequelize adapter ,因為它可以在創建模型時自由的設定你自己的 ID 。
資料庫 adapters 也實作了 CRUD 操作。這些 Action 是已被發布的方法,可以被其它服務呼叫使用。
find
Query 查詢實體。支援快取。
參數:
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
populate |
<String[]> | - | 欄位填充清單 |
fields |
<String[]> | - | 欄位過濾清單 |
limit |
<Number> | 必填 |
最大資料數 |
offset |
<Number> | 必填 |
跳過資料數 |
sort |
<String> | 必填 |
排序設定 |
search |
<String> | 必填 |
查詢的文字 |
searchFields |
<String> | 必填 |
查詢的欄位 |
query |
<Object> | 必填 |
Query 查詢。直接傳遞給 adapter 。 |
響應:
<Object[]> - 查詢到的實體物件清單。
count
Query 查詢實體數量。支援快取。
參數:
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
search |
<String> | 必填 |
查詢的文字 |
searchFields |
<String> | 必填 |
查詢的欄位 |
query |
<Object> | 必填 |
Query 查詢。直接傳遞給 adapter 。 |
響應:
<Number> - 查詢到的實體物件數量。
list
使用過濾與分頁查詢實體清單。支援快取。
參數:
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
populate |
<String[]> | - | 欄位填充清單 |
fields |
<String[]> | - | 欄位過濾清單 |
page |
<Number> | 必填 |
分頁數 |
pageSize |
<Number> | 必填 |
分頁大小 |
sort |
<String> | 必填 |
排序設定 |
search |
<String> | 必填 |
查詢的文字 |
searchFields |
<String> | 必填 |
查詢的欄位 |
query |
<Object> | 必填 |
Query 查詢。直接傳遞給 adapter 。 |
響應:
<Object> - 查詢到的實體物件清單及數量。
create
建立一個新的實體物件。
參數:
響應:
<Object> - 已建立的實體物件。
insert
建立多個新的實體物件。
參數:
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
entity |
<Object> | - | 實體物件 |
entities |
<Object[]> | - | 多個實體物件 |
響應:
<Object> | <Object[]> - 已建立的實體物件或多個實體物件。
get
由 ID 取得實體物件。支援快取。
參數:
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
id |
<Any> | <Any[]> | 必填 |
ID 或多個 ID |
populate |
<String[]> | - | 欄位填充清單 |
fields |
<String[]> | - | 欄位過濾清單 |
mapping |
<Boolean> | - | 將響應的陣列轉為物件型態,並將 ID 設為鍵值。 |
響應:
<Object> | <Object[]> - 查詢到實體物件或清單。
update
由 ID 更新實體物件。
注意,更新後請記得清除快取並呼叫生命週期事件。
參數:
響應:
<Object> - 已更新的實體物件。
remove
由 ID 刪除實體物件。
參數:
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
id |
<Any> | <Any[]> | 必填 |
ID 或多個 ID |
響應:
<Number> - 已刪除的實體物件數量。
資料庫 adapters 也實作了輔助的方法。
getById
由一個或多個 ID 取得一個或多個實體物件。
參數:
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
id |
<String> | <Number> | <Array> | 必填 |
ID 或多個 ID |
decoding |
<Boolean> | 必填 |
ID 是否需要解碼 |
響應:
<Object> | <Object[]> - 查詢到的一個或多個實體物件。
clearCache
清除快取實體物件。
參數:
響應:
<Promise> - 空的 Promise 物件。
encodeID
編碼實體物件 ID 。
參數:
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
id |
<Any> | 必填 |
ID |
響應:
<Any> - 編碼後的 ID。
decodeID
解碼實體物件 ID 。
參數:
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
id |
<Any> | 必填 |
未解碼的 ID |
響應:
<Any> - 解碼後的 ID。
注意,只有支援的 Adapter 編解碼才有效果。
_find
Query 查詢實體。支援快取。
參數:
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
populate |
<String[]> | - | 欄位填充清單 |
fields |
<String[]> | - | 欄位過濾清單 |
limit |
<Number> | 必填 |
最大資料數 |
offset |
<Number> | 必填 |
跳過資料數 |
sort |
<String> | 必填 |
排序設定 |
search |
<String> | 必填 |
查詢的文字 |
searchFields |
<String> | 必填 |
查詢的欄位 |
query |
<Object> | 必填 |
Query 查詢。直接傳遞給 adapter 。 |
響應:
<Object[]> - 查詢到的實體物件清單。
_count
使用過濾與分頁查詢實體清單。支援快取。
參數:
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
populate |
<String[]> | - | 欄位填充清單 |
fields |
<String[]> | - | 欄位過濾清單 |
page |
<Number> | 必填 |
分頁數 |
pageSize |
<Number> | 必填 |
分頁大小 |
sort |
<String> | 必填 |
排序設定 |
search |
<String> | 必填 |
查詢的文字 |
searchFields |
<String> | 必填 |
查詢的欄位 |
query |
<Object> | 必填 |
Query 查詢。直接傳遞給 adapter 。 |
響應:
<Object> - 查詢到的實體物件清單及數量。
_create
建立一個新的實體物件。
參數:
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
params |
<Object> | - | 實體物件 |
響應:
<Object> - 已建立的實體物件。
_insert
建立多個新的實體物件。
參數:
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
entity |
<Object> | - | 實體物件 |
entities |
<Object[]> | - | 多個實體物件 |
響應:
<Object> | <Object[]> - 已建立的實體物件或多個實體物件。
_get
由 ID 取得實體物件。支援快取。
參數:
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
id |
<Any> | <Any[]> | 必填 |
ID 或多個 ID |
populate |
<String[]> | - | 欄位填充清單 |
fields |
<String[]> | - | 欄位過濾清單 |
mapping |
<Boolean> | - | 將響應的陣列轉為物件型態,並將 ID 設為鍵值。 |
響應:
<Object> | <Object[]> - 查詢到實體物件或清單。
_update
由 ID 更新實體物件。
注意,更新後請記得清除快取並呼叫生命週期事件。
參數:
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
params |
<Object> | - | 欲更新的實體物件 |
響應:
<Object> - 已更新的實體物件。
_remove
由 ID 刪除實體物件。
參數:
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
id |
<Any> | 必填 |
實體物件 ID |
響應:
<Number> - 已刪除的實體物件數量。
你可以使用 Action Hooks ,在資料儲存之前變更要儲存進資料庫的資料,或是在取得資料後變更輸出的資料。
範例:使用 Hooks 加入時間戳記與移除敏感資訊
"use strict";
const { ServiceBroker } = require("moleculer");
const DbService = require("moleculer-db");
const broker = new ServiceBroker();
broker.createService({
name: "db-with-hooks",
// 載入資料庫 actions
mixins: [DbService],
// 在資料庫 action 建立 Hooks
hooks: {
before: {
create: [
function addTimestamp(ctx) {
// 加入時間戳記
ctx.params.createdAt = new Date();
return ctx;
}
]
},
after: {
get: [
// 使用箭頭函數建立 Hook
(ctx, res) => {
// 移除敏感資訊
delete res.mail;
delete res.phoneNumber;
return res;
}
]
}
}
});
const user = {
name: "John Doe",
mail: "john.doe@example.com",
phoneNumber: 123456789
};
broker.start()
// 將使用者資料寫入資料庫
// 呼叫 "create" action 之前會先觸發 hook
.then(() => broker.call("db-with-hooks.create", user))
// 由資料庫取得使用者
// 呼叫 "get" action 之前會先觸發 hook
.then(entry => broker.call("db-with-hooks.get", { id: entry._id }))
.then(res => console.log(res))
.catch(err => console.error(err));
你可以輕鬆的透過 populates
設定來由其它服務填充欄位。例如,你的 post
實體物件中有一個 author
欄位,你可以在 users
服務由 ID 來取得資料填充到 author
中。如果欄位是一個陣列且有多個 ID 時,僅需一個請求就會填充到所有的實體物件。
填充參數可用於
find
、list
、get
。
範例:填充綱目
broker.createService({
name: "posts",
mixins: [DbService],
settings: {
populates: {
// 速記填充規則。由 `users.get` 來填充 `voters` 的值
"voters": "users.get",
// 定義 Action 呼叫時的參數。它只會將 `username` 與 `fullName` 填充到 `author`
"author": {
action: "users.get",
params: {
fields: "username fullName"
}
},
// 在這個例子中,原始的 `reviewerId` 欄位不應該被覆蓋填充值。
// 它會由 `reviewerId` 呼叫 `users.get` 取得資料,
// 但是會將 `username` 與 `fullName` 填充到 `reviewer` 。
"reviewer": {
field: "reviewerId",
action: "users.get",
params: {
fields: "username fullName"
}
},
// 客製化填充處理函數
"rate"(ids, items, rule, ctx) {
// `items` 參數是響應的陣列內容
// 它與 Promise.resolve 的資料無關,你需要的是客製化填充 `items` 。
// 如有疑問請參閱原始碼[5]
return Promise.resolve(...);
}
}
}
});
// 查詢 posts 清單並填充 `authors`
broker.call("posts.find", { populate: ["author"] }).then(console.log);
進行實體物件操作的時候,會呼叫 3 個生命週期事件。
注意,如果是使用
updateMany
、removeMany
操作多個實體物件,則json
參數將會是一個Number
而不是一個實體物件。
broker.createService({
name: "posts",
mixins: [DbService],
settings: {},
afterConnected() {
this.logger.info("Connected successfully");
},
entityCreated(json, ctx) {
this.logger.info("New entity created!");
},
entityUpdated(json, ctx) {
// 你可以在此訪問 context
this.logger.info(`Entity updated by '${ctx.meta.user.name}' user!`);
},
entityRemoved(json, ctx) {
this.logger.info("Entity removed", json);
},
});
你也可以在服務中加入客製化的 Action 來完成所需的功能。
const DbService = require("moleculer-db");
module.exports = {
name: "posts",
mixins: [DbService],
settings: {
fields: ["_id", "title", "content", "votes"]
},
actions: {
// 由 `id` 更新累加 `votes` 欄位
vote(ctx) {
return this.adapter.updateById(ctx.params.id, { $inc: { votes: 1 } });
},
// 查詢 `authorID` 的 `posts` 清單
byAuthors(ctx) {
return this.find({
query: {
author: ctx.params.authorID
},
limit: ctx.params.limit || 10,
sort: "-createdAt"
});
}
}
};
基於 MongoDB[6] 的 Adapter 。
使用前請安裝 MongoDB 相關套件 npm install moleculer-db moleculer-db-adapter-mongo --save
。
MongoDB Adapter 需要依賴 MongoDB[7] ,你必須在你的系統安裝它。
範例:
"use strict";
const { ServiceBroker } = require("moleculer");
const DbService = require("moleculer-db");
const MongoDBAdapter = require("moleculer-db-adapter-mongo");
const broker = new ServiceBroker();
// 建立一個 Mongodb 的 `posts` 實體物件
broker.createService({
name: "posts",
mixins: [DbService],
adapter: new MongoDBAdapter("mongodb://localhost/moleculer-demo"),
collection: "posts"
});
broker.start()
// 建立一個新 `post`
.then(() => broker.call("posts.create", {
title: "My first post",
content: "Lorem ipsum...",
votes: 0
}))
// 取得所有 `posts`
.then(() => broker.call("posts.find").then(console.log));
範例:使用 URI 及選項
new MongoDBAdapter("mongodb://localhost/moleculer-db", {
keepAlive: 1
})
更多 Mongo Adapter 範例請參閱 moleculer-db Github[8] 。
基於 Mongoose[9] 的 Adapter 。
使用前請安裝 Mongoose 相關套件 npm install moleculer-db moleculer-db-adapter-mongoose mongoose --save
。
Mongoose Adapter 需要依賴 MongoDB[7] ,你必須在你的系統安裝它。
範例:
"use strict";
const { ServiceBroker } = require("moleculer");
const DbService = require("moleculer-db");
const MongooseAdapter = require("moleculer-db-adapter-mongoose");
const mongoose = require("mongoose");
const broker = new ServiceBroker();
// 建立一個 Mongoose 的 `posts` 實體物件
broker.createService({
name: "posts",
mixins: [DbService],
adapter: new MongooseAdapter("mongodb://localhost/moleculer-demo"),
model: mongoose.model("posts", mongoose.Schema({
title: { type: String },
content: { type: String },
votes: { type: Number, default: 0 }
}))
});
broker.start()
// 建立一個新 `post`
.then(() => broker.call("posts.create", {
title: "My first post",
content: "Lorem ipsum...",
votes: 0
}))
// 取得所有 `posts`
.then(() => broker.call("posts.find").then(console.log));
範例:使用 URI 及選項
new MongooseAdapter("mongodb://localhost/moleculer-db", {
user: process.env.MONGO_USERNAME,
pass: process.env.MONGO_PASSWORD
keepAlive: true
})
如果你的服務部屬在多個節點上,且希望能連線到多個資料庫,那麼你可以直接在服務中定義 model
就好。但如果你的服務僅部屬在一個節點上,又希望能連線到多個資料庫,那麼則應該要定義 schema
來建立多個連線的服務。
更多 Mongoose Adapter 範例請參閱 moleculer-db Github[10] 。
Moleculer DB 使用 Sequelize
[11] ORM 工具來開發,它可支援 Postgres
、 MySQL
、 SQLite
、 MSSQL
的 SQL Adapter。
npm install moleculer-db-adapter-sequelize --save
請根據資料庫需求安裝對應的資料庫套件:
SQLite
此資料庫會自動生成 SQLite[12] 資料庫檔案。
npm install sqlite3 --save
MySQL
此資料庫依賴 MySQL[13] ,你必須在你的系統安裝它。
npm install mysql2 --save
PostgreSQL
此資料庫依賴 PostgreSQL[14] ,你必須在你的系統安裝它。
npm install pg pg-hstore --save
MSSQL
此資料庫依賴 MSSQL[15] ,你必須在你的系統安裝它。
npm install tedious --save
範例:
"use strict";
const { ServiceBroker } = require("moleculer");
const DbService = require("moleculer-db");
const SqlAdapter = require("moleculer-db-adapter-sequelize");
const Sequelize = require("sequelize");
const broker = new ServiceBroker();
// 建立一個 Sequelize 的 `posts` 實體物件
broker.createService({
name: "posts",
mixins: [DbService],
adapter: new SqlAdapter("sqlite://:memory:"),
model: {
name: "post",
define: {
title: Sequelize.STRING,
content: Sequelize.TEXT,
votes: Sequelize.INTEGER,
author: Sequelize.INTEGER,
status: Sequelize.BOOLEAN
},
options: {
// 選項請參閱:
// https://sequelize.org/docs/v6/moved/models-definition/
}
},
});
broker.start()
// 建立一個新 `post`
.then(() => broker.call("posts.create", {
title: "My first post",
content: "Lorem ipsum...",
votes: 0
}))
// 取得所有 `posts`
.then(() => broker.call("posts.find").then(console.log));
範例:使用 URI
new SqlAdapter("postgres://user:pass@example.com:5432/dbname")
範例:使用選項連線
new SqlAdapter('database', 'username', 'password', {
host: 'localhost',
dialect: 'mysql'|'sqlite'|'postgres'|'mssql',
pool: {
max: 5,
min: 0,
idle: 10000
},
noSync: true // 不同步。Sequelize 將不會同步 Model
// SQLite 檔案路徑
storage: 'path/to/database.sqlite'
})
更多 Mongoose Adapter 範例請參閱 moleculer-db Github[16] 。
更多的資料庫 Adapters 請參閱官方手冊:
https://moleculer.services/modules.html#databases
[1] Database Adapters, https://moleculer.services/docs/0.14/moleculer-db.html
[2] Pattern: Database per service, https://microservices.io/patterns/data/database-per-service.html
[3] DB Adapters (moleculer-db), https://moleculer.services/docs/0.14/faq.html#DB-Adapters-moleculer-db
[4] NeDB, https://github.com/louischatriot/nedb
[5] moleculer-db@0.8.19, https://github.com/moleculerjs/moleculer-db/blob/moleculer-db%400.8.19/packages/moleculer-db/src/index.js#L589-L658
[6] MongoDB Node.js Driver, https://mongodb.github.io/node-mongodb-native/
[7] MongoDB, https://www.mongodb.com/
[8] moleculer-db-adapter-mongo examples, https://github.com/moleculerjs/moleculer-db/tree/master/packages/moleculer-db-adapter-mongo/examples
[9] mongoose, https://mongoosejs.com/docs/
[10] moleculer-db-adapter-mongoose examples, https://github.com/moleculerjs/moleculer-db/tree/master/packages/moleculer-db-adapter-mongoose/examples
[11] Sequelize, https://github.com/sequelize/sequelize
[12] SQLite, https://www.sqlite.org/index.html
[13] MySQL, https://www.mysql.com/
[14] PostgreSQL, https://www.postgresql.org/
[15] MSSQL, https://www.microsoft.com/sql-server
[16] moleculer-db-adapter-sequelize examples, https://github.com/moleculerjs/moleculer-db/tree/master/packages/moleculer-db-adapter-sequelize/examples