seneca mysql_Seneca :NodeJS 微效劳框架入门指南
Seneca 是一个能让您疾速构建基于音讯的微效劳体系的东西集,你不须要晓得种种效劳自身被布置在那边,不须要晓得细致有若干效劳存在,也不须要晓得他们细致做什么,任何你营业逻辑以外的效劳(如数据库、缓存或许第三方集成等)都被隐蔽在微效劳以后。
这类解耦使您的体系易于一连构建与更新,Seneca 能做到这些,缘由在于它的三大中心功用:
情势婚配:差别于软弱的效劳发明,情势婚配旨在关照这个天下你真正体贴的音讯是什么;
无依靠传输:你能够以多种体式格局在效劳之间发送音讯,一切这些都隐蔽至你的营业逻辑以后;
组件化:功用被示意为一组能够一同构成微效劳的插件。
在 Seneca 中,音讯就是一个能够有任何你喜好的内部构造的 JSON 对象,它们能够经由过程 HTTP/HTTPS、TCP、音讯行列、宣布/定阅效劳或许任何能传输数据的体式格局举行传输,而关于作为音讯临盆者的你来讲,你只须要将音讯发送出去即可,完全不须要体贴哪些效劳来吸收它们。
然后,你又想关照这个天下,你想要吸收一些音讯,这也很简朴,你只需在 Seneca 中作一点婚配情势设置即可,婚配情势也很简朴,只是一个键值对的列表,这些键值对被用于婚配 JSON 音讯的极组属性。
在本文接下来的内容中,我们将一同基于 Seneca 构建一些微效劳。
情势( Patterns )
让我们从一点迥殊简朴的代码最先,我们将竖立两个微效劳,一个会举行数学盘算,另一个去挪用它:
const seneca = require('seneca')();
seneca.add('role:math, cmd:sum', (msg, reply) => {
reply(null, { answer: ( msg.left + msg.right )})
});
seneca.act({
role: 'math',
cmd: 'sum',
left: 1,
right: 2
}, (err, result) => {
if (err) {
return console.error(err);
}
console.log(result);
});
将上面的代码,保留至一个 js 文件中,然后实行它,你能够会在 console 中看到相似下面如许的音讯:
{"kind":"notice","notice":"hello seneca 4y8daxnikuxp/1483577040151/58922/3.2.2/-","level":"info","when":1483577040175}
(node:58922) DeprecationWarning: 'root' is deprecated, use 'global'
{ answer: 3 }
到目前为止,一切这一切都发作在同一个历程中,没有网络流量发作,历程内的函数挪用也是基于音讯传输。
seneca.add 要领,增添了一个新的行动情势(_Action Pattern_)至 Seneca 实例中,它有两个参数:
pattern :用于婚配 Seneca 实例中 JSON 音讯体的情势;
action :当情势被婚配时实行的操纵
seneca.act 要领一样有两个参数:
msg :作为纯对象供应的待婚配的入站音讯;
respond :用于吸收并处置惩罚响应信息的回调函数。
让我们再把一切代码从新过一次:
seneca.add('role:math, cmd:sum', (msg, reply) => {
reply(null, { answer: ( msg.left + msg.right )})
});
在上面的代码中的 Action 函数,盘算了婚配到的音讯体中两个属性 left 与 right 的值的和,并非一切的音讯都邑被竖立一个响应,然则在绝大多数情况下,是须要有响应的, Seneca 供应了用于响应音讯的回调函数。
在婚配情势中, role:math, cmd:sum 婚配到了下面这个音讯体:
{
role: 'math',
cmd: 'sum',
left: 1,
right: 2
}
并取得计自结果:
{
answer: 3
}
关于 role 与 cmd 这两个属性,它们没有什么迥殊的,只是正好被你用于婚配情势罢了。
接着,seneca.act 要领,发送了一条音讯,它有两个参数:
msg :发送的音讯主体
response_callback :假如该音讯有任何响应,该回调函数都邑被实行。
响应的回调函数可吸收两个参数: error 与 result ,假如有任何毛病发作(比方,发送出去的音讯未被任何情势婚配),则第一个参数将是一个 Error 对象,而假如递次依据我们所预期的方向实行了的话,那末,第二个参数将吸收到响应结果,在我们的示例中,我们只是简朴的将吸收到的响应结果打印至了 console 罢了。
seneca.act({
role: 'math',
cmd: 'sum',
left: 1,
right: 2
}, (err, result) => {
if (err) {
return console.error(err);
}
console.log(result);
});
sum.js 示例文件,向你展现了怎样定义并竖立一个 Action 以及怎样呼起一个 Action,但它们都发作在一个历程中,接下来,我们很快就会展现怎样拆分红差别的代码和多个历程。
婚配情势怎样事变?
情势—-而不是网络地点或许会话,让你能够越发轻易的扩大或加强您的体系,如许做,让增添新的微效劳变得更简朴。
如今让我们给体系再增添一个新的功用—-盘算两个数字的乘积。
我们想要发送的音讯看起来像下面如许的:
{
role: 'math',
cmd: 'product',
left: 3,
right: 4
}
然后取得的结果看起来像下面如许的:
{
answer: 12
}
晓得怎么做了吧?你能够像 role: math, cmd: sum 情势如许,竖立一个 role: math, cmd: product 操纵:
seneca.add('role:math, cmd:product', (msg, reply) => {
reply(null, { answer: ( msg.left * msg.right )})
});
然后,挪用该操纵:
seneca.act({
role: 'math',
cmd: 'product',
left: 3,
right: 4
}, (err, result) => {
if (err) {
return console.error(err);
}
console.log(result);
});
运转 product.js ,你将取得你想要的结果。
将这两个要领放在一同,代码像是下面如许的:
const seneca = require('seneca')();
seneca.add('role:math, cmd:sum', (msg, reply) => {
reply(null, { answer: ( msg.left + msg.right )})
});
seneca.add('role:math, cmd:product', (msg, reply) => {
reply(null, { answer: ( msg.left * msg.right )})
});
seneca.act({role: 'math', cmd: 'sum', left: 1, right: 2}, console.log)
.act({role: 'math', cmd: 'product', left: 3, right: 4}, console.log)
运转 sum-product.js 后,你将取得下面如许的结果:
null { answer: 3 }
null { answer: 12 }
在上面兼并到一同的代码中,我们发明, seneca.act 是能够举行链式挪用的,Seneca 供应了一个链式API,调式挪用是递次实行的,然则不是串行,所以,返回的结果的递次能够与挪用递次并不一样。
扩大情势以增添新功用
情势让你能够越发轻易的扩大递次的功用,与 if...else... 语法差别的是,你能够经由过程增添更多的婚配情势以到达一样的功用。
下面让我们扩大一下 role: math, cmd: sum 操纵,它只吸收整型数字,那末,怎么做?
seneca.add({role: 'math', cmd: 'sum', integer: true}, function (msg, respond) {
var sum = Math.floor(msg.left) + Math.floor(msg.right)
respond(null, {answer: sum})
})
如今,下面这条音讯:
{role: 'math', cmd: 'sum', left: 1.5, right: 2.5, integer: true}
将取得下面如许的结果:
{answer: 3} // == 1 + 2,小数部份已被移除了
如今,你的两个情势都存在于体系中了,而且还存在交织部份,那末 Seneca 终究会将音讯婚配至哪条情势呢?原则是:更多婚配项目被婚配到的优先,被婚配到的属性越多,则优先级越高。
const seneca = require('seneca')()
seneca.add({role: 'math', cmd: 'sum'}, function (msg, respond) {
var sum = msg.left + msg.right
respond(null, {answer: sum})
})
// 下面两条音讯都婚配 role: math, cmd: sum
seneca.act({role: 'math', cmd: 'sum', left: 1.5, right: 2.5}, console.log)
seneca.act({role: 'math', cmd: 'sum', left: 1.5, right: 2.5, integer: true}, console.log)
setTimeout(() => {
seneca.add({role: 'math', cmd: 'sum', integer: true}, function (msg, respond) {
var sum = Math.floor(msg.left) + Math.floor(msg.right)
respond(null, { answer: sum })
})
// 下面这条音讯一样婚配 role: math, cmd: sum
seneca.act({role: 'math', cmd: 'sum', left: 1.5, right: 2.5}, console.log)
// 然则,也婚配 role:math,cmd:sum,integer:true
// 然则因为更多属性被婚配到,所以,它的优先级更高
seneca.act({role: 'math', cmd: 'sum', left: 1.5, right: 2.5, integer: true}, console.log)
}, 100)
输出结果应当像下面如许:
null { answer: 4 }
null { answer: 4 }
null { answer: 4 }
null { answer: 3 }
在上面的代码中,因为体系中只存在 role: math, cmd: sum 情势,所以,都婚配到它,然则当 100ms 后,我们给体系中增添了一个 role: math, cmd: sum, integer: true 情势以后,结果就不一样了,婚配到更多的操纵将有更高的优先级。
这类设想,能够让我们的体系能够越发简朴的增添新的功用,不管是在开辟环境照样在临盆环境中,你都能够在不须要修正现有代码的前提下即可更新新的效劳,你只须要先好新的效劳,然后启动新效劳即可。
基于情势的代码复用
情势操纵还能够挪用别的的操纵,所以,如许我们能够到达代码复用的需求:
const seneca = require('seneca')()
seneca.add('role: math, cmd: sum', function (msg, respond) {
var sum = msg.left + msg.right
respond(null, {answer: sum})
})
seneca.add('role: math, cmd: sum, integer: true', function (msg, respond) {
// 复用 role:math, cmd:sum
this.act({
role: 'math',
cmd: 'sum',
left: Math.floor(msg.left),
right: Math.floor(msg.right)
}, respond)
})
// 婚配 role:math,cmd:sum
seneca.act('role: math, cmd: sum, left: 1.5, right: 2.5',console.log)
// 婚配 role:math,cmd:sum,integer:true
seneca.act('role: math, cmd: sum, left: 1.5, right: 2.5, integer: true', console.log)
在上面的示例代码中,我们运用了 this.act 而不是前面的 seneca.act,那是因为,在 action 函数中,上下文关联变量 this ,援用了当前的 seneca 实例,如许你就能够在任何一个 action 函数中,接见到该 action 挪用的悉数上下文。
在上面的代码中,我们运用了 JSON 缩写情势来形貌情势与音讯, 比方,下面是对象字面量:
{role: 'math', cmd: 'sum', left: 1.5, right: 2.5}
缩写情势为:
'role: math, cmd: sum, left: 1.5, right: 2.5'
jsonic 这类花样,供应了一种以字符串字面量来表达对象的轻便体式格局,这使得我们能够竖立越发简朴的情势和音讯。
上面的代码保留在了 sum-reuse.js 文件中。
情势是唯一的
你定义的 Action 情势都是唯一了,它们只能触发一个函数,情势的剖析划定规矩以下:
更多我属性优先级更高
若情势具有雷同的数目标属性,则按字母递次婚配
划定规矩被设想得很简朴,这使得你能够越发简朴的相识到究竟是哪一个情势被婚配了。
下面这些示例能够让你更轻易明白:
a: 1, b: 2 优先于 a: 1, 因为它有更多的属性;
a: 1, b: 2 优先于 a: 1, c: 3,因为 b 在 c 字母的前面;
a: 1, b: 2, d: 4 优先于 a: 1, c: 3, d:4,因为 b 在 c 字母的前面;
a: 1, b:2, c:3 优先于 a:1, b: 2,因为它有更多的属性;
a: 1, b:2, c:3 优先于 a:1, c:3,因为它有更多的属性。
许多时刻,供应一种能够让你不须要通盘修正现有 Action 函数的代码即可增添它功用的要领是很有必要的,比方,你能够想为某一个音讯增添更多自定义的属性考证要领,捕捉音讯统计信息,增添分外的数据库结果中,或许掌握音讯流速等。
我下面的示例代码中,加法操纵希冀 left 和 right 属性是有限数,另外,为了调试目标,将原始输入参数附加到输出的结果中也是很有用的,您能够运用以下代码增添考证搜检和调试信息:
const seneca = require('seneca')()
seneca
.add(
'role:math,cmd:sum',
function(msg, respond) {
var sum = msg.left + msg.right
respond(null, {
answer: sum
})
})
// 重写 role:math,cmd:sum with ,增添分外的功用
.add(
'role:math,cmd:sum',
function(msg, respond) {
// bail out early if there's a problem
if (!Number.isFinite(msg.left) ||
!Number.isFinite(msg.right)) {
return respond(new Error("left 与 right 值必需为数字。"))
}
// 挪用上一个操纵函数 role:math,cmd:sum
this.prior({
role: 'math',
cmd: 'sum',
left: msg.left,
right: msg.right,
}, function(err, result) {
if (err) return respond(err)
result.info = msg.left + '+' + msg.right
respond(null, result)
})
})
// 增添了的 role:math,cmd:sum
.act('role:math,cmd:sum,left:1.5,right:2.5',
console.log // 打印 { answer: 4, info: '1.5+2.5' }
)
seneca 实例供应了一个名为 prior 的要领,让能够在当前的 action 要领中,挪用被其重写的旧操纵函数。
prior 函数接收两个参数:
msg:音讯体
response_callback:回调函数
在上面的示例代码中,已演示了怎样修正入介入出参,修正这些参数与值是可选的,比方,能够再增添新的重写,以增添日记纪录功用。
在上面的示例中,也一样演示了怎样更好的举行毛病处置惩罚,我们在真正举行操纵之前,就考证的数据的准确性,若传入的参数自身就有毛病,那末我们直接就返回毛病信息,而不须要守候真正盘算的时刻由体系去报错了。
毛病音讯应当只被用于形貌毛病的输入或许内部失利信息等,比方,假如你实行了一些数据库的查询,返回没有任何数据,这并非一个毛病,而仅仅只是数据库的现实的反应,然则假如衔接数据库失利,那就是一个毛病了。
上面的代码能够在 sum-valid.js 文件中找到。
运用插件构造情势
一个 seneca 实例,实在就只是多个 Action Patterm 的鸠合罢了,你能够运用定名空间的体式格局来构造操纵情势,比方在前面的示例中,我们都运用了 role: math,为了协助日记纪录和调试, Seneca 还支撑一个简约的插件支撑。
一样,Seneca插件只是一组操纵情势的鸠合,它能够有一个称号,用于解释日记纪录条目,还能够给插件一组选项来掌握它们的行动,插件还供应了以准确的递次实行初始化函数的机制,比方,您愿望在尝试从数据库读取数据之前竖立数据库衔接。
简朴来讲,Seneca插件就只是一个具有单个参数选项的函数,你将这个插件定义函数通报给 seneca.use 要领,下面这个是最小的Seneca插件(实在它什么也没做!):
function minimal_plugin(options) {
console.log(options)
}
require('seneca')()
.use(minimal_plugin, {foo: 'bar'})
seneca.use 要领接收两个参数:
plugin :插件定义函数或许一个插件称号;
options :插件设置选项
上面的示例代码实行后,打印出来的日记看上去是如许的:
{"kind":"notice","notice":"hello seneca 3qk0ij5t2bta/1483584697034/62768/3.2.2/-","level":"info","when":1483584697057}
(node:62768) DeprecationWarning: 'root' is deprecated, use 'global'
{ foo: 'bar' }
Seneca 还供应了细致日记纪录功用,能够供应为开辟或许临盆供应更多的日记信息,一般的,日记级别被设置为 INFO,它并不会打印太多日记信息,假如想看到一切的日记信息,尝尝以下面如许的体式格局启动你的效劳:
node minimal-plugin.js --seneca.log.all
会不会被吓一跳?固然,你还能够过滤日记信息:
node minimal-plugin.js --seneca.log.all | grep plugin:define
经由过程日记我们能够看到, seneca 加载了许多内置的插件,比方 basic、transport、web 以及 mem-store,这些插件为我们供应了竖立微效劳的基本功用,一样,你应当也能够看到 minimal_plugin 插件。
如今,让我们为这个插件增添一些操纵情势:
function math(options) {
this.add('role:math,cmd:sum', function (msg, respond) {
respond(null, { answer: msg.left + msg.right })
})
this.add('role:math,cmd:product', function (msg, respond) {
respond(null, { answer: msg.left * msg.right })
})
}
require('seneca')()
.use(math)
.act('role:math,cmd:sum,left:1,right:2', console.log)
运转 math-plugin.js 文件,取得下面如许的信息:
null { answer: 3 }
看打印出来的一条日记:
{
"actid": "7ubgm65mcnfl/uatuklury90r",
"msg": {
"role": "math",
"cmd": "sum",
"left": 1,
"right": 2,
"meta$": {
"id": "7ubgm65mcnfl/uatuklury90r",
"tx": "uatuklury90r",
"pattern": "cmd:sum,role:math",
"action": "(bjx5u38uwyse)",
"plugin_name": "math",
"plugin_tag": "-",
"prior": {
"chain": [],
"entry": true,
"depth": 0
},
"start": 1483587274794,
"sync": true
},
"plugin$": {
"name": "math",
"tag": "-"
},
"tx$": "uatuklury90r"
},
"entry": true,
"prior": [],
"meta": {
"plugin_name": "math",
"plugin_tag": "-",
"plugin_fullname": "math",
"raw": {
"role": "math",
"cmd": "sum"
},
"sub": false,
"client": false,
"args": {
"role": "math",
"cmd": "sum"
},
"rules": {},
"id": "(bjx5u38uwyse)",
"pattern": "cmd:sum,role:math",
"msgcanon": {
"cmd": "sum",
"role": "math"
},
"priorpath": ""
},
"client": false,
"listen": false,
"transport": {},
"kind": "act",
"case": "OUT",
"duration": 35,
"result": {
"answer": 3
},
"level": "debug",
"plugin_name": "math",
"plugin_tag": "-",
"pattern": "cmd:sum,role:math",
"when": 1483587274829
}
一切的该插件的日记都被自动的增添了 plugin 属性。
在 Seneca 的天下中,我们经由过程插件构造种种操纵情势鸠合,这让日记与调试变得更简朴,然后你还能够将多个插件兼并成为种种微效劳,在接下来的章节中,我们将竖立一个 math 效劳。
插件经由过程须要举行一些初始化的事变,比方衔接数据库等,然则,你并不须要在插件的定义函数中去实行这些初始化,定义函数被设想为同步实行的,因为它的一切操纵都是在定义一个插件,现实上,你不该当在定义函数中挪用 seneca.act 要领,只挪用 seneca.add 要领。
要初始化插件,你须要定义一个特别的婚配情势 init: ,关于每个插件,将按递次挪用此操纵情势,init 函数必需挪用其 callback 函数,而且不能有毛病发作,假如插件初始化失利,则 Seneca 会马上退出 Node 历程。所以的插件初始化事变都必需在任何操纵实行之前完成。
为了演示初始化,让我们向 math 插件增添简朴的自定义日记纪录,当插件启动时,它翻开一个日记文件,并将一切操纵的日记写入文件,文件须要胜利翻开而且可写,假如这失利,微效劳启动就应当失利。
const fs = require('fs')
function math(options) {
// 日记纪录函数,经由过程 init 函数竖立
var log
// 将一切情势放在一同会上我们查找更轻易
this.add('role:math,cmd:sum', sum)
this.add('role:math,cmd:product', product)
// 这就是谁人特别的初始化操纵
this.add('init:math', init)
function init(msg, respond) {
// 将日记纪录至一个特写的文件中
fs.open(options.logfile, 'a', function (err, fd) {
// 假如不能读取或许写入该文件,则返回毛病,这会致使 Seneca 启动失利
if (err) return respond(err)
log = makeLog(fd)
respond()
})
}
function sum(msg, respond) {
var out = { answer: msg.left + msg.right }
log('sum '+msg.left+'+'+msg.right+'='+out.answer+'\n')
respond(null, out)
}
function product(msg, respond) {
var out = { answer: msg.left * msg.right }
log('product '+msg.left+'*'+msg.right+'='+out.answer+'\n')
respond(null, out)
}
function makeLog(fd) {
return function (entry) {
fs.write(fd, new Date().toISOString()+' '+entry, null, 'utf8', function (err) {
if (err) return console.log(err)
// 确保日记条目已革新
fs.fsync(fd, function (err) {
if (err) return console.log(err)
})
})
}
}
}
require('seneca')()
.use(math, {logfile:'./math.log'})
.act('role:math,cmd:sum,left:1,right:2', console.log)
在上面这个插件的代码中,婚配情势被构造在插件的顶部,以便它们更轻易被看到,函数在这些情势下面一点被定义,您还能够看到怎样运用选项供应自定义日记文件的位置(显而易见,这不是临盆日记!)。
初始化函数 init 实行一些异步文件体系事变,因而必需在实行任何操纵之前完成。 假如失利,悉数效劳将没法初始化。要检察失利时的操纵,能够尝试将日记文件位置更改成无效的,比方 /math.log。
竖立微效劳
如今让我们把 math 插件变成一个真正的微效劳。起首,你须要构造你的插件。 math 插件的营业逻辑 —- 即它供应的功用,与它以何种体式格局与外部天下通讯是离开的,你能够会暴露一个Web效劳,也有能够在音讯总线上监听。
将营业逻辑(即插件定义)放在其本身的文件中是有意义的。 Node.js 模块即可圆满的完成,竖立一个名为 math.js 的文件,内容以下:
module.exports = function math(options) {
this.add('role:math,cmd:sum', function sum(msg, respond) {
respond(null, { answer: msg.left + msg.right })
})
this.add('role:math,cmd:product', function product(msg, respond) {
respond(null, { answer: msg.left * msg.right })
})
this.wrap('role:math', function (msg, respond) {
msg.left = Number(msg.left).valueOf()
msg.right = Number(msg.right).valueOf()
this.prior(msg, respond)
})
}
然后,我们能够在须要援用它的文件中像下面如许增添到我们的微效劳体系中:
// 下面这两种体式格局都是等价的(还记得我们前面讲过的 `seneca.use` 要领的两个参数吗?)
require('seneca')()
.use(require('./math.js'))
.act('role:math,cmd:sum,left:1,right:2', console.log)
require('seneca')()
.use('math') // 在当前目录下找到 `./math.js`
.act('role:math,cmd:sum,left:1,right:2', console.log)
seneca.wrap 要领能够婚配一组情势,同运用雷同的行动扩大函数掩盖至一切被婚配的情势,这与为每个组情势手动挪用 seneca.add 去扩大能够取得一样的结果,它须要两个参数:
pin :情势婚配情势
action :扩大的 action 函数
pin 是一个能够婚配到多个情势的情势,它能够婚配到多个情势,比方 role:math 这个 pin 能够婚配到 role:math, cmd:sum 与 role:math, cmd:product。
在上面的示例中,我们在末了面的 wrap 函数中,确保了,任何通报给 role:math 的音讯体中 left 与 right 值都是数字,纵然我们通报了字符串,也能够被自动的转换为数字。
偶然,检察 Seneca 实例中有哪些操纵是被重写了是很有用的,你能够在启动运用时,加上 --seneca.print.tree 参数即可,我们先竖立一个 math-tree.js 文件,填入以下内容:
require('seneca')()
.use('math')
然后再实行它:
❯ node math-tree.js --seneca.print.tree
{"kind":"notice","notice":"hello seneca abs0eg4hu04h/1483589278500/65316/3.2.2/-","level":"info","when":1483589278522}
(node:65316) DeprecationWarning: 'root' is deprecated, use 'global'
Seneca action patterns for instance: abs0eg4hu04h/1483589278500/65316/3.2.2/-
├─┬ cmd:sum
│ └─┬ role:math
│ └── # math, (15fqzd54pnsp),
│ # math, (qqrze3ub5vhl), sum
└─┬ cmd:product
└─┬ role:math
└── # math, (qnh86mgin4r6),
# math, (4nrxi5f6sp69), product
从上面你能够看到许多的键/值对,而且以树状构造展现了重写,一切的 Action 函数展现的花样都是 #plugin, (action-id), function-name。
然则,到如今为止,一切的操纵都还存在于同一个历程中,接下来,让我们先竖立一个名为 math-service.js 的文件,填入以下内容:
require('seneca')()
.use('math')
.listen()
然后启动该剧本,即可启动我们的微效劳,它会启动一个历程,并经由过程 10101 端口监听HTTP要求,它不是一个 Web 效劳器,在此时, HTTP 仅仅作为音讯的传输机制。
curl -d '{"role":"math","cmd":"sum","left":1,"right":2}' http://localhost:10101/act
两种体式格局都能够看到结果:
{"answer":3}
接下来,你须要一个微效劳客户端 math-client.js:
require('seneca')()
.client()
.act('role:math,cmd:sum,left:1,right:2',console.log)
翻开一个新的终端,实行该剧本:
null { answer: 3 } { id: '7uuptvpf8iff/9wfb26kbqx55',
accept: '043di4pxswq7/1483589685164/65429/3.2.2/-',
track: undefined,
time:
{ client_sent: '0',
listen_recv: '0',
listen_sent: '0',
client_recv: 1483589898390 } }
在 Seneca 中,我们经由过程 seneca.listen 要领竖立微效劳,然后经由过程 seneca.client 去与微效劳举行通讯。在上面的示例中,我们运用的都是 Seneca 的默许设置,比方 HTTP 协定监听 10101 端口,但 seneca.listen 与 seneca.client 要领都能够接收下面这些参数,以到达定抽的功用:
port :可选的数字,示意端口号;
host :可先的字符串,示意主机名或许IP地点;
spec :可选的对象,完全的定制对象
注重:在 Windows 体系中,假如未指定
host, 默许会衔接
0.0.0.0,这是没有任何用途的,你能够设置
host 为
localhost。
只需 client 与 listen 的端口号与主机一致,它们就能够举行通讯:
seneca.client(8080) → seneca.listen(8080)
seneca.client(8080, ‘192.168.0.2’) → seneca.listen(8080, ‘192.168.0.2’)
seneca.client({ port: 8080, host: ‘192.168.0.2’ }) → seneca.listen({ port: 8080, host: ‘192.168.0.2’ })
Seneca 为你供应的 无依靠传输 特征,让你在举行营业逻辑开辟时,不须要晓得音讯怎样传输或哪些效劳会取得它们,而是在效劳设置代码或设置中指定,比方 math.js 插件中的代码永久不须要转变,我们就能够恣意的转变传输体式格局。
虽然 HTTP 协定很轻易,然则并非一切时刻都适宜,另一个经常使用的协定是 TCP,我们能够很轻易的运用 TCP 协定来举行数据的传输,尝试下面这两个文件:
require('seneca')()
.use('math')
.listen({type: 'tcp'})
require('seneca')()
.client({type: 'tcp'})
.act('role:math,cmd:sum,left:1,right:2',console.log)
默许情况下, client/listen 并未指定哪些音讯将发送至那里,只是当地定义了情势的话,会发送至当地的情势中,不然会悉数发送至效劳器中,我们能够经由过程一些设置来定义哪些音讯将发送到哪些效劳中,你能够运用一个 pin 参数来做这件事变。
让我们来竖立一个运用,它将经由过程 TCP 发送一切 role:math 音讯至效劳,而把别的的一切音讯都在发送至当地:
require('seneca')()
.use('math')
// 监听 role:math 音讯
// 主要:必需婚配客户端
.listen({ type: 'tcp', pin: 'role:math' })
require('seneca')()
// 当地情势
.add('say:hello', function (msg, respond){ respond(null, {text: "Hi!"}) })
// 发送 role:math 情势至效劳
// 注重:必需婚配效劳端
.client({ type: 'tcp', pin: 'role:math' })
// 长途操纵
.act('role:math,cmd:sum,left:1,right:2',console.log)
// 当地操纵
.act('say:hello',console.log)
你能够经由过程种种过滤器来自定义日记的打印,以跟踪音讯的活动,运用 --seneca... 参数,支撑以下设置:
date-time: log 条目什么时候被竖立;
seneca-id: Seneca process ID;
level:DEBUG、INFO、WARN、ERROR 以及 FATAL 中任何一个;
type:条目编码,比方 act、plugin 等;
plugin:插件称号,不是插件内的操纵将示意为 root$;
case: 条目标事宜:IN、ADD、OUT 等
action-id/transaction-id:跟踪标识符,_在网络中永久坚持一致_;
pin:action 婚配情势;
message:入/出参音讯体
假如你运转上面的历程,运用了 --seneca.log.all,则会打印出一切日记,假如你只想看 math 插件打印的日记,能够像下面如许启动效劳:
node math-pin-service.js --seneca.log=plugin:math
Web 效劳集成
Seneca不是一个Web框架。 然则,您依然须要将其衔接到您的Web效劳API,你永久要记着的是,不要将你的内部行动情势暴露在表面,这不是一个好的平安的实践,相反的,你应当定义一组API情势,比方用属性 role:api,然后你能够将它们衔接到你的内部微效劳。
下面是我们定义 api.js 插件。
module.exports = function api(options) {
var validOps = { sum:'sum', product:'product' }
this.add('role:api,path:calculate', function (msg, respond) {
var operation = msg.args.params.operation
var left = msg.args.query.left
var right = msg.args.query.right
this.act('role:math', {
cmd: validOps[operation],
left: left,
right: right,
}, respond)
})
this.add('init:api', function (msg, respond) {
this.act('role:web',{routes:{
prefix: '/api',
pin: 'role:api,path:*',
map: {
calculate: { GET:true, suffix:'/{operation}' }
}
}}, respond)
})
}
然后,我们运用 hapi 作为Web框架,建了 hapi-app.js 运用:
const Hapi = require('hapi');
const Seneca = require('seneca');
const SenecaWeb = require('seneca-web');
const config = {
adapter: require('seneca-web-adapter-hapi'),
context: (() => {
const server = new Hapi.Server();
server.connection({
port: 3000
});
server.route({
path: '/routes',
method: 'get',
handler: (request, reply) => {
const routes = server.table()[0].table.map(route => {
return {
path: route.path,
method: route.method.toUpperCase(),
description: route.settings.description,
tags: route.settings.tags,
vhost: route.settings.vhost,
cors: route.settings.cors,
jsonp: route.settings.jsonp,
}
})
reply(routes)
}
});
return server;
})()
};
const seneca = Seneca()
.use(SenecaWeb, config)
.use('math')
.use('api')
.ready(() => {
const server = seneca.export('web/context')();
server.start(() => {
server.log('server started on: ' + server.info.uri);
});
});
启动 hapi-app.js 以后,接见 http://localhost:3000/routes,你便能够看到下面如许的信息:
[
{
"path": "/routes",
"method": "GET",
"cors": false
},
{
"path": "/api/calculate/{operation}",
"method": "GET",
"cors": false
}
]
这示意,我们已胜利的将情势婚配更新至 hapi 运用的路由中。接见 http://localhost:3000/api/cal… ,将取得结果:
{"answer":3}
在上面的示例中,我们直接将 math 插件也加载到了 seneca 实例中,实在我们能够越发合理的举行这类操纵,如 hapi-app-client.js 文件所示:
...
const seneca = Seneca()
.use(SenecaWeb, config)
.use('api')
.client({type: 'tcp', pin: 'role:math'})
.ready(() => {
const server = seneca.export('web/context')();
server.start(() => {
server.log('server started on: ' + server.info.uri);
});
});
我们不注册 math 插件,而是运用 client 要领,将 role:math 发送给 math-pin-service.js 的效劳,而且运用的是 tcp 衔接,没错,你的微效劳就是如许成型了。
注重:永久不要运用外部输入竖立操纵的音讯体,永久显现地在内部竖立,这能够有用防备注入进击。
在上面的的初始化函数中,挪用了一个 role:web 的情势操纵,而且定义了一个 routes 属性,这将定义一个URL地点与操纵情势的婚配划定规矩,它有下面这些参数:
prefix:URL 前缀
pin: 须要映照的情势集
map:要用作 URL Endpoint 的 pin 通配符属性列表
你的URL地点将最先于 /api/。
rol:api, path:* 这个 pin 示意,映照任何有 role="api" 键值对,同时 path 属性被定义了的情势,在本例中,只要 role:api,path:calculate 符合该情势。
map 属性是一个对象,它有一个 calculate 属性,对应的URL地点最先于:/api/calculate。
按着, calculate 的值是一个对象,它示意了 HTTP 的 GET 要领是被许可的,而且URL应当有参数化的后缀(后缀就类于 hapi 的 route 划定规矩中一样)。
所以,你的完全地点是 /api/calculate/{operation}。
然后,别的的音讯属性都将从 URL query 对象或许 JSON body 中取得,在本示例中,因为运用的是 GET 要领,所以没有 body。
SenecaWeb 将会经由过程 msg.args 来形貌一次要求,它包含:
body:HTTP 要求的 payload 部份;
query:要求的 querystring;
params:要求的途径参数。
如今,启动前面我们竖立的微效劳:
node math-pin-service.js --seneca.log=plugin:math
然后再启动我们的运用:
node hapi-app.js --seneca.log=plugin:web,plugin:api
接见下面的地点:
数据耐久化
一个实在的体系,一定须要耐久化数据,在Seneca中,你能够实行任何您喜好的操纵,运用任何范例的数据库层,然则,为何不运用情势婚配和微效劳的气力,使你的开辟更轻松?
情势婚配还意味着你能够推延有关微效劳数据的争辩,比方效劳是不是应当”具有”数据,效劳是不是应当接见共享数据库等,情势婚配意味着你能够在随后的任什么时候刻从新设置你的体系。
seneca-entity 供应了一个简朴的数据笼统层(ORM),基于以下操纵:
load:依据实体标识加载一个实体;
save:竖立或更新(假如你供应了一个标识的话)一个实体;
list:列出婚配查询前提的一切实体;
remove:删除一个标识指定的实体。
它们的婚配情势分别是:
load: role:entity,cmd:load,name:
save: role:entity,cmd:save,name:
list: role:entity,cmd:list,name:
remove: role:entity,cmd:remove,name:
任何完成了这些情势的插件都能够被用于供应数据库(比方 MySQL)接见。
当数据的耐久化与别的的一切都基于雷同的机制供应时,微效劳的开辟将变得更轻易,而这类机制,就是情势婚配音讯。
因为直接运用数据耐久性情势能够变得乏味,所以 seneca 实体还供应了一个更熟习的 ActiveRecord 作风的接口,要竖立纪录对象,请挪用 seneca.make 要领。 纪录对象有要领 load$、save$、list$ 以及 remove$(一切要领都带有 $ 后缀,以防备与数据字段争执),数据字段只是对象属性。
经由过程 npm 装置 seneca-entity, 然后在你的运用中运用 seneca.use() 要领加载至你的 seneca 实例。
如今让我们先竖立一个简朴的数据实体,它保留 book 的概况。
const seneca = require('seneca')();
seneca.use('basic').use('entity');
const book = seneca.make('book');
book.title = 'Action in Seneca';
book.price = 9.99;
// 发送 role:entity,cmd:save,name:book 音讯
book.save$( console.log );
在上面的示例中,我们还运用了 seneca-basic,它是 seneca-entity 依靠的插件。
实行上面的代码以后,我们能够看到下面如许的日记:
❯ node book.js
null $-/-/book;id=byo81d;{title:Action in Seneca,price:9.99}
Seneca 内置了
mem-store,这使得我们在本示例中,不须要运用任何别的数据库的支撑也能举行完全的数据库耐久操纵(虽然,它并非真正的耐久化了)。
因为数据的耐久化永久都是运用的一样的音讯情势集,所以,你能够异常简朴的交互数据库,比方,你能够在开辟的过程当中运用的是 MongoDB,然后,开辟完成以后,在临盆环境中运用 Postgres。
下面让我他竖立一个简朴的线上书店,我们能够经由过程它,疾速的增添新书、猎取书的细致信息以及购置一本书:
module.exports = function(options) {
// 从数据库中,查询一本ID为 `msg.id` 的书,我们运用了 `load$` 要领
this.add('role:store, get:book', function(msg, respond) {
this.make('book').load$(msg.id, respond);
});
// 向数据库中增添一本书,书的数据为 `msg.data`,我们运用了 `data$` 要领
this.add('role:store, add:book', function(msg, respond) {
this.make('book').data$(msg.data).save$(respond);
});
// 竖立一条新的付出定单(在实在的体系中,经常是由商品概况布中的 *购置* 按钮触
// 发的事宜),先是查询出ID为 `msg.id` 的书本,若查询失足,则直接返回毛病,
// 不然,将书本的信息复制给 `purchase` 实体,并保留该定单,然后,我们发送了
// 一条 `role:store,info:purchase` 音讯(然则,我们并不吸收任何响应),
// 这条音讯只是关照悉数体系,我们如今有一条新的定单发作了,然则我并不体贴谁会
// 须要它。
this.add('role:store, cmd:purchase', function(msg, respond) {
this.make('book').load$(msg.id, function(err, book) {
if (err) return respond(err);
this
.make('purchase')
.data$({
when: Date.now(),
bookId: book.id,
title: book.title,
price: book.price,
})
.save$(function(err, purchase) {
if (err) return respond(err);
this.act('role:store,info:purchase', {
purchase: purchase
});
respond(null, purchase);
});
});
});
// 末了,我们完成了 `role:store, info:purchase` 情势,就只是简朴的将信息
// 打印出来, `seneca.log` 对象供应了 `debug`、`info`、`warn`、`error`、
// `fatal` 要领用于打印响应级别的日记。
this.add('role:store, info:purchase', function(msg, respond) {
this.log.info('purchase', msg.purchase);
respond();
});
};
接下来,我们能够竖立一个简朴的单元测试,以考证我们前面竖立的递次:
// 运用 Node 内置的 `assert` 模块
const assert = require('assert')
const seneca = require('seneca')()
.use('basic')
.use('entity')
.use('book-store')
.error(assert.fail)
// 增添一本书
addBook()
function addBook() {
seneca.act(
'role:store,add:book,data:{title:Action in Seneca,price:9.99}',
function(err, savedBook) {
this.act(
'role:store,get:book', {
id: savedBook.id
},
function(err, loadedBook) {
assert.equal(loadedBook.title, savedBook.title)
purchase(loadedBook);
}
)
}
)
}
function purchase(book) {
seneca.act(
'role:store,cmd:purchase', {
id: book.id
},
function(err, purchase) {
assert.equal(purchase.bookId, book.id)
}
)
}
实行该测试:
❯ node book-store-test.js
["purchase",{"entity$":"-/-/purchase","when":1483607360925,"bookId":"a2mlev","title":"Action in Seneca","price":9.99,"id":"i28xoc"}]
在一个临盆运用中,我们关于上面的定单数据,能够会有零丁的效劳举行监控,而不是像上面如许,只是打印一条日记出来,那末,我们如今来竖立一个新的效劳,用于网络定单数据:
const stats = {};
require('seneca')()
.add('role:store,info:purchase', function(msg, respond) {
const id = msg.purchase.bookId;
stats[id] = stats[id] || 0;
stats[id]++;
console.log(stats);
respond();
})
.listen({
port: 9003,
host: 'localhost',
pin: 'role:store,info:purchase'
});
然后,更新 book-store-test.js 文件:
const seneca = require('seneca')()
.use('basic')
.use('entity')
.use('book-store')
.client({port:9003,host: 'localhost', pin:'role:store,info:purchase'})
.error(assert.fail);
此时,当有新的定单发作时,就会关照到定单监控效劳了。
将一切效劳集成到一同
经由过程上面的一切步骤,我们如今已有四个效劳了:
book-store-stats 与 math-pin-service 我们已有了,所以,直接启动即可:
node math-pin-service.js --seneca.log.all
node book-store-stats.js --seneca.log.all
如今,我们须要一个 book-store-service :
require('seneca')()
.use('basic')
.use('entity')
.use('book-store')
.listen({
port: 9002,
host: 'localhost',
pin: 'role:store'
})
.client({
port: 9003,
host: 'localhost',
pin: 'role:store,info:purchase'
});
该效劳吸收任何 role:store 音讯,但同时又将任何 role:store,info:purchase 音讯发送至网络,永久都要记着, client 与 listen 的 pin 设置必需完全一致。
如今,我们能够启动该效劳:
node book-store-service.js --seneca.log.all
然后,竖立我们的 app-all.js,首选,复制 api.js 文件到 api-all.js,这是我们的API。
module.exports = function api(options) {
var validOps = {
sum: 'sum',
product: 'product'
}
this.add('role:api,path:calculate', function(msg, respond) {
var operation = msg.args.params.operation
var left = msg.args.query.left
var right = msg.args.query.right
this.act('role:math', {
cmd: validOps[operation],
left: left,
right: right,
}, respond)
});
this.add('role:api,path:store', function(msg, respond) {
let id = null;
if (msg.args.query.id) id = msg.args.query.id;
if (msg.args.body.id) id = msg.args.body.id;
const operation = msg.args.params.operation;
const storeMsg = {
role: 'store',
id: id
};
if ('get' === operation) storeMsg.get = 'book';
if ('purchase' === operation) storeMsg.cmd = 'purchase';
this.act(storeMsg, respond);
});
this.add('init:api', function(msg, respond) {
this.act('role:web', {
routes: {
prefix: '/api',
pin: 'role:api,path:*',
map: {
calculate: {
GET: true,
suffix: '/{operation}'
},
store: {
GET: true,
POST: true,
suffix: '/{operation}'
}
}
}
}, respond)
})
}
const Hapi = require('hapi');
const Seneca = require('seneca');
const SenecaWeb = require('seneca-web');
const config = {
adapter: require('seneca-web-adapter-hapi'),
context: (() => {
const server = new Hapi.Server();
server.connection({
port: 3000
});
server.route({
path: '/routes',
method: 'get',
handler: (request, reply) => {
const routes = server.table()[0].table.map(route => {
return {
path: route.path,
method: route.method.toUpperCase(),
description: route.settings.description,
tags: route.settings.tags,
vhost: route.settings.vhost,
cors: route.settings.cors,
jsonp: route.settings.jsonp,
}
})
reply(routes)
}
});
return server;
})()
};
const seneca = Seneca()
.use(SenecaWeb, config)
.use('basic')
.use('entity')
.use('math')
.use('api-all')
.client({
type: 'tcp',
pin: 'role:math'
})
.client({
port: 9002,
host: 'localhost',
pin: 'role:store'
})
.ready(() => {
const server = seneca.export('web/context')();
server.start(() => {
server.log('server started on: ' + server.info.uri);
});
});
// 竖立一本示例书本
seneca.act(
'role:store,add:book', {
data: {
title: 'Action in Seneca',
price: 9.99
}
},
console.log
)
启动该效劳:
node app-all.js --seneca.log.all
从掌握台我们能够看到下面如许的音讯:
null $-/-/book;id=0r7mg7;{title:Action in Seneca,price:9.99}
这示意胜利竖立了一本ID为 0r7mg7 的书本,如今,我们接见 http://localhost:3000/api/store/get?id=0r7mg7 即可检察该ID的书本概况(ID是随机的,所以,你天生的ID能够并非如许的)。
然后我们可竖立一个新的购置定单:
curl -d '{"id":"0r7mg7"}' -H "content-type:application/json" http://localhost:3000/api/store/purchase
{"when":1483609872715,"bookId":"0r7mg7","title":"Action in Seneca","price":9.99,"id":"8suhf4"}
最好 Seneca 运用构造实践
引荐你如许做
将营业逻辑与实行离开,放在零丁的插件中,比方差别的Node模块、差别的项目以至同一个项目下差别的文件都是能够的;
运用实行剧本撰写您的运用递次,不要畏惧为差别的上下文运用差别的剧本,它们看上去应当很短,比方像下面如许:
var SOME_CONFIG = process.env.SOME_CONFIG || 'some-default-value'
require('seneca')({ some_options: 123 })
// 已存在的 Seneca 插件
.use('community-plugin-0')
.use('community-plugin-1', {some_config: SOME_CONFIG})
.use('community-plugin-2')
// 营业逻辑插件
.use('project-plugin-module')
.use('../plugin-repository')
.use('./lib/local-plugin')
.listen( ... )
.client( ... )
.ready( function() {
// 当 Seneca 启动胜利以后的自定义剧本
})
插件加载递次很主要,这固然是一件功德,能够主上你对音讯的成有相对的掌握权。
不引荐你如许做
将 Seneca 运用的启动与初始化同别的框架的启动与初始化放在一同了,永久记着,坚持事件的简朴;
将 Seneca 实例当作变量随处通报。
seneca mysql_Seneca :NodeJS 微效劳框架入门指南相关推荐
- Seneca :NodeJS 微服务框架入门指南
Seneca :NodeJS 微服务框架入门指南 原文:http://onmr.com/press/getting-started-seneca.html Seneca 是一个能让您快速构建基于消息的 ...
- Seneca:NodeJS 微服务框架入门(一)
Seneca是什么? (1)官网是这样介绍的: Seneca is a microservices toolkit for Node.js. It helps you write clean, org ...
- nodejs微服务框架解决方案
前言 seneca是一个nodejs微服务工具集,它赋予系统易于连续构建和更新的能力.下面会逐一和大家一起了解相关技术入门以及实践. 这里插入一段硬广.小子再进行简单整合之后撸了个vastify框架 ...
- 基于Groovy的Spock单元测试框架入门指南
文章目录 1. Spock Primer 基本概念 1.1 对比Junit 1.2 Imports 1.3 Specification测试类 1.4 Fields 属性 1.5 Fixture Met ...
- go-zero微服务框架入门教程
为什么使用go-zero 你还在手撕微服务?快试试 go-zero 的微服务自动生成神器,这可能是我见过最简单好用的微服务框架. 还有比它更简单好用的吗?欢迎留言评论和推荐. 几分钟搞定个接口和微服务 ...
- Titan框架入门指南:Titan如何工作
这是该系列的第三篇文章,到目前为止,我已经讨论了Titan框架的重要性和功能以及基本设置. 在第一篇文章中,我讨论了Titan框架的三步设置,如下所示: 设置您的项目. 创建选项. 获取价值. 我在上 ...
- nodejs微服务解决方案
前言 seneca是一个nodejs微服务工具集,它赋予系统易于连续构建和更新的能力.下面会逐一和大家一起了解相关技术入门以及实践. 这里插入一段硬广.小子再进行简单整合之后撸了个vastify框架 ...
- Vue.js 入门指南之“前传”(含sublime text 3 配置)
题记:关注Vue.js 很久了,但就是没有动手写过一行代码,今天准备入手,却发现自己比菜鸟还菜,于是四方寻找大牛指点,才终于找到了入门的"入门",就算是"入门指南&quo ...
- node.js Web应用框架Express入门指南
node.js Web应用框架Express入门指南 作者: 字体:[增加 减小] 类型:转载 时间:2014-05-28 我要评论 这篇文章主要介绍了node.js Web应用框架Express入门 ...
最新文章
- 创新的前端 豆瓣书评 摘录
- 石油大c语言答案,中国石油大学C语言答案
- java怎么显示qt文件后缀,在qt中执行java文件
- java 拉丁文 unicode_“java语言使用的是Unicode编码”是指的jvm?.java文件?
- RUNOOB python练习题 32 列表的中括号符号小tips
- XML解析-Dom4j的DOM解析方式更新XML
- 疫情之下,精准测试的智能可信模式正在成为中流砥柱
- 人的差别在于业余时间——细细品味 ==程序员学习能力提升三要素 ==》程序员学习能力提升三要素 ==编程从业五年的十四条经验,句句朴实
- 读《白话统计》笔记——第七章
- 尚学堂浪曦视频学习推荐顺序
- Mybatis实现mysql分页查询
- C -- OC with RunTime
- 在word中同时输入上下标设置
- MybatisPlusException: This is impossible to happen
- 利用adobe acrobat裁剪PDF
- CMake入门1——CMake与VS编译器和nmake的结合使用
- Teamvier提示商业用途限制使用的解决方案
- Qiyuan-python接小球游戏
- 北京奇云计算机技术学校,好的计算机编程学校
- 基于回旋曲线的平行泊车路径规划
热门文章
- 人工智能(Artificial Intelligence),英文缩写为AI
- WordPress Qui-Pure V2.22发布纯文本主题-暗黑模式
- 【视觉算法】SHOT特征描述子
- 硬件服务器搭建系统步骤,服务器硬件部署方案
- payload什么意思_代码参数里的 payload 是什么意思???
- code函数oracle列子,Oracle 8 的函数介绍_oracle
- The code signature version is no longer supported.
- Docker 容器start | restart | stop
- Java自动生成PDF并进行邮件群发
- esp12s 第七章 ESP-NOW互相传送