微服务开发
华炎魔方基于 Moleculer 微服务架构,可以使用 nodejs 或 java 扩展微服务。
方案有一些主要部件: name
, version
, settings
, actions
, methods
, events
.
// math.service.js
module.exports = {
name: "math",
actions: {
add(ctx) {
return Number(ctx.params.a) + Number(ctx.params.b);
},
sub(ctx) {
return Number(ctx.params.a) - Number(ctx.params.b);
}
}
}
服务方案中有一些基础属性。
// posts.v1.service.js
module.exports = {
name: "posts",
version: 1
}
name
是一个必须定义的属性。 当你调用它时,它是动作名称的第一部分。
要禁用服务名称前缀, 请在服务设置中修改
$noServiceNamePrefix: true
。
version
是一个可选的属性。 使用它来运行来自同一服务的多个版本。 它也是动作名称中的前缀。 它可以是 Number
或 String
。
// posts.v2.service.js
module.exports = {
name: "posts",
version: 2,
actions: {
find() {...}
}
}
在版本 2
上调用此服务的动作 find
:
broker.call("v2.posts.find");
{% note info REST call %}
通过API Gateway, 发出请求 GET /v2/posts/find
.
{% endnote %}
要禁用服务名称前缀, 请在服务设置中修改
$noVersionPrefix: true
。
settings
属性是一个静态存储属性,您可以在那里存储每个设置/选项到您的服务。 您可以通过服务内的 this.settings
操作它。
// mailer.service.js
module.exports = {
name: "mailer",
settings: {
transport: "mailgun"
},
action: {
send(ctx) {
if (this.settings.transport == "mailgun") {
...
}
}
}
}
settings
也可以在远程节点上获得。 它在发现服务期间转移。
核心模块使用了一些内部设置。 这些设置名称具有 $
(dollar sign) 前缀。
Name | Type | Default | 说明 |
---|---|---|---|
$noVersionPrefix | Boolean | false | 在动作名称中禁用版本前缀。 |
$noServiceNamePrefix | Boolean | false | 在动作名称中禁用名字前缀。 |
$dependencyTimeout | Number | 0 | 等待此服务的依赖服务超时时间。 |
$shutdownTimeout | Number | 0 | 超时关闭此等待的活动请求。 |
$secureSettings | Array | [] | 安全设置列表。 |
为了保护您的令牌 & API 密钥,在服务设置中定义 $secureSettings: []
属性并设置受保护的属性键。 受保护的设置不会被发布到其他节点,它不会出现在服务注册表中。 这些设置仅在服务函数内的 this.settings
下可用。
// mail.service.js
module.exports = {
name: "mailer",
settings: {
$secureSettings: ["transport.auth.user", "transport.auth.pass"],
from: "sender@moleculer.services",
transport: {
service: 'gmail',
auth: {
user: 'gmail.user@gmail.com',
pass: 'yourpass'
}
}
}
// ...
};
Mixins 是分配 Moleculer 服务的可重复使用功能的一种灵活的方式。 服务构造器将这些混入功能与当前服务方案合并。 当一个服务使用混入时,mixins 中存在的所有属性都将被“混入”到当前的服务中。
以下示例扩展 moleculer-web
服务
// api.service.js
const ApiGwService = require("moleculer-web");
module.exports = {
name: "api",
mixins: [ApiGwService]
settings: {
// Change port setting
port: 8080
},
actions: {
myAction() {
// Add a new action to apiGwService service
}
}
}
上面的示例创建一个 api
服务,继承来自 ApiGwService
的所有属性,但覆盖端口设置并通过新的 myAction
动作扩展。
合并算法取决于属性类型。
Property | Algorithm |
---|---|
name , version | 合并 & 覆盖。 |
settings | 在 defaultsDeep 的情况下深度扩展。 |
metadata | 在 defaultsDeep 的情况下深度扩展。 |
actions | 在 defaultsDeep 的情况下深度扩展。 如果您在服务中设置为 fals 您可以禁用混入行为。 |
hooks | 在 defaultsDeep 的情况下深度扩展。 |
methods | 合并 & 覆盖。 |
events | 连接侦听器。 |
created , started , stopped | 连接侦听器。 |
mixins | 合并 & 覆盖。 |
dependencies | 合并 & 覆盖。 |
any custom | 合并 & 覆盖。 |
{% note info Merge algorithm examples %}
合并 & 覆盖: 如果 serviceA 有 a: 5
, b: 8
且 serviceB 有c: 10
b: 15
, 混合服务将有 a: 5
, b: 15
and c: 10
Concatenate: 如果 serviceA & serviceB 订阅 users.created
事件,当 users.created
事件发出时,两个事件处理程序都会被调用。
{% endnote %}
服务公开的可调用的方法称为活动或动作或行为 (actions, 以后不加区分)。 他们可以使用 broker.call
或 ctx.call
来调用。 该动作可以是 Function
(简写为 handler) 或一个对象具有 handler
属性及更多属性。 该动作应该放置在方案中的 actions
下. 参见 actions documentation.
// math.service.js
module.exports = {
name: "math",
actions: {
// Shorthand definition, only a handler function
add(ctx) {
return Number(ctx.params.a) + Number(ctx.params.b);
},
// Normal definition with other properties. In this case
// the `handler` function is required!
mult: {
cache: false,
params: {
a: "number",
b: "number"
},
handler(ctx) {
// The action properties are accessible as `ctx.action.*`
if (!ctx.action.cache)
return Number(ctx.params.a) * Number(ctx.params.b);
}
}
}
};
您可以这样调用上述动作
const res = await broker.call("math.add", { a: 5, b: 7 });
const res = await broker.call("math.mult", { a: 10, b: 31 });
在动作中,您可以使用 ctx.call
方法在其他服务中调用其他嵌套动作。 它是 broker.call
的一个别名,但它将自己设置为父 context (由于正确的追踪链)。
// posts.service.js
module.exports = {
name: "posts",
actions: {
async get(ctx) {
// Find a post by ID
let post = posts[ctx.params.id];
// Populate the post.author field through "users" service
// Call the "users.get" action with author ID
const user = await ctx.call("users.get", { id: post.author });
if (user) {
// Replace the author ID with the received user object
post.author = user;
}
return post;
}
}
};
动作处理器的
this
总是指向 Service 实例。
您可以在 events
中订阅事件。 参见 events documentation.
// report.service.js
module.exports = {
name: "report",
events: {
// Subscribe to "user.created" event
"user.created"(ctx) {
this.logger.info("User created:", ctx.params);
// Do something
},
// Subscribe to all "user.*" events
"user.*"(ctx) {
console.log("Payload:", ctx.params);
console.log("Sender:", ctx.nodeID);
console.log("Metadata:", ctx.meta);
console.log("The called event name:", ctx.eventName);
}
// Subscribe to a local event
"$node.connected"(ctx) {
this.logger.info(`Node '${ctx.params.id}' is connected!`);
}
}
};
动作处理器的
this
总是指向 Service 实例。
服务管理器按群组名称将事件监听器分组。 默认情况下,群组名称是服务名称。 但你可以在事件定义中覆盖它。
// payment.service.js
module.exports = {
name: "payment",
events: {
"order.created": {
// Register handler to the "other" group instead of "payment" group.
group: "other",
handler(payload) {
// ...
}
}
}
}
若要在服务中创建私有方法,请将您的函数放在 methods
中。 这些函数是私有的,无法与 broker.call
一起调用。 但你可以在服务中调用它(来自操作处理器、事件处理器和生命周期事件处理器)。
用例
// mailer.service.js
module.exports = {
name: "mailer",
actions: {
send(ctx) {
// Call the `sendMail` method
return this.sendMail(ctx.params.recipients, ctx.params.subject, ctx.params.body);
}
},
methods: {
// Send an email to recipients
sendMail(recipients, subject, body) {
return new Promise((resolve, reject) => {
...
});
}
}
};
If you want to wrap a method with a middleware use the following notation:
// posts.service.js
module.exports = {
name: "posts",
methods: {
list: {
async handler(count) {
// Do something
return posts;
}
}
}
};
方法名称不能是
name
,version
,settings
,metadata
,schema
,broker
,actions
,logger
, 因为在方案中这些名字是保留的。
方法中的
this
总是指向 Service 实例。
有一些生命周期服务事件,将由服务管理器触发。 它们放置服务方案中。
// www.service.js
module.exports = {
name: "www",
actions: {...},
events: {...},
methods: {...},
created() {
// Fired when the service instance created (with `broker.loadService` or `broker.createService`)
},
merged() {
// Fired after the service schemas merged and before the service instance created
},
async started() {
// Fired when broker starts this service (in `broker.start()`)
}
async stopped() {
// Fired when broker stops this service (in `broker.stop()`)
}
};
如果您的服务依赖于其他服务,请使用方案中的 dependencies
属性。 服务在调用 started
事件之前会等待它的依赖服务处理完毕。
// posts.service.js
module.exports = {
name: "posts",
settings: {
$dependencyTimeout: 30000 // Default: 0 - no timeout
},
dependencies: [
"likes", // shorthand w/o version
"v2.auth", // shorthand w version
{ name: "users", version: 2 }, // with numeric version
{ name: "comments", version: "staging" } // with string version
],
async started() {
this.logger.info("It will be called after all dependent services are available.");
const users = await this.broker.call("users.list");
}
....
}
一旦 likes
, v2.auth
, v2.users
, staging.comments
(无论以上服务在本地还是远程节点) 变得可用,服务立即执行 started
处理器。
可以使用 ServiceBroker
的方法 waitForServices
来等待服务。 所有定义的服务可用 & 启动后, 它返回的 Promise
将被解决。
Parameters
Parameter | Type | Default | Description |
---|---|---|---|
services | String or Array | - | 等待服务列表 |
timeout | Number | 0 | 等待超时。 0 意味着没有超时。 如果超时,引发 MoleculerServerError 。 |
interval | Number | 1000 | 以毫秒为单位的监视频率 |
示例
broker.waitForServices(["posts", "v2.users"]).then(() => {
// Called after the `posts` & `v2.users` services are available
});
设置超时 & 间隔
broker.waitForServices("accounts", 10 * 1000, 500).then(() => {
// Called if `accounts` service becomes available in 10 seconds
}).catch(err => {
// Called if service is not available in 10 seconds
});
Service
方案有一个 metadata
属性。 您可以在此存储任何关于服务的元信息。 您可以在服务函数内用 this.metadata
访问它。 Moleculer 核心模块不使用它, 你可以用它存储任何东西。
module.exports = {
name: "posts",
settings: {},
metadata: {
scalable: true,
priority: 5
},
actions: { ... }
};
metadata
也可以在远程节点上获得。 它在服务发现期间转移。