{"id":13454335,"url":"https://github.com/senecajs/seneca","last_synced_at":"2025-05-15T02:01:56.705Z","repository":{"id":1135113,"uuid":"1012745","full_name":"senecajs/seneca","owner":"senecajs","description":"A microservices toolkit for Node.js.","archived":false,"fork":false,"pushed_at":"2025-02-07T16:55:37.000Z","size":8947,"stargazers_count":3967,"open_issues_count":210,"forks_count":310,"subscribers_count":137,"default_branch":"master","last_synced_at":"2025-05-08T01:48:38.374Z","etag":null,"topics":["javascript","microservices","node","seneca"],"latest_commit_sha":null,"homepage":"http://senecajs.org","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/senecajs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2010-10-21T17:09:05.000Z","updated_at":"2025-04-28T17:41:04.000Z","dependencies_parsed_at":"2023-01-13T10:58:05.732Z","dependency_job_id":"ef9cd3c4-d182-4f9e-8b14-ccf2fa4d11f6","html_url":"https://github.com/senecajs/seneca","commit_stats":{"total_commits":1580,"total_committers":63,"mean_commits":25.07936507936508,"dds":0.2683544303797468,"last_synced_commit":"89580d8f818e9b3b77878e8d5aadf56f108e57ff"},"previous_names":[],"tags_count":152,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/senecajs%2Fseneca","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/senecajs%2Fseneca/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/senecajs%2Fseneca/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/senecajs%2Fseneca/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/senecajs","download_url":"https://codeload.github.com/senecajs/seneca/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253755528,"owners_count":21959028,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","microservices","node","seneca"],"created_at":"2024-07-31T08:00:53.177Z","updated_at":"2025-05-15T02:01:55.976Z","avatar_url":"https://github.com/senecajs.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","Packages","Repository","包","目录","Web frameworks","Service Toolkits"],"sub_categories":["Web frameworks","Web Frameworks","Web 框架","Web框架","Node.js"],"readme":"![Logo][]\n\u003e A Node.js toolkit for Microservice architectures\n\n\n| ![Voxgig](https://www.voxgig.com/res/img/vgt01r.png) | This open source module is sponsored and supported by [Voxgig](https://www.voxgig.com). |\n|---|---|\n\n\n# Seneca\n[![Npm][BadgeNpm]][Npm]\n[![NpmFigs][BadgeNpmFigs]][Npm]\n[![Travis][BadgeTravis]][Travis]\n[![Coveralls][BadgeCoveralls]][Coveralls]\n[![DeepScan][BadgeDeepScan]][DeepScan]\n[![CodeClimate][BadgeCodeClimate]][CodeClimate]\n[![Gitter][BadgeGitter]][Gitter]\n\n- __Lead Maintainer:__ [Richard Rodger][Lead]\n- __Sponsor:__ [voxgig][Sponsor]\n\nSeneca is a toolkit for writing microservices and organizing the\nbusiness logic of your app. You can break down your app into \"stuff\nthat happens\", rather than focusing on data models or managing\ndependencies.\n\nSeneca provides,\n\n- __pattern matching:__ a wonderfully flexible way to handle business requirements\n\n- __transport independence:__ how messages get to the right server is not something you\nshould have to worry about\n\n- __maturity:__ 8 years in production (before we called it _microservices_), but was\nonce taken out by [lightning][]\n\n- __plus:__ a deep and wide ecosystem of [plugins][]\n\n- __book:__ a guide to designing microservice architectures: [taomicro][]\n\nUse this module to define commands that work by taking in some JSON,\nand, optionally, returning some JSON. The command to run is selected\nby pattern-matching on the the input JSON. There are built-in and\noptional sets of commands that help you build Minimum Viable Products:\ndata storage, user management, distributed logic, caching, logging,\netc. And you can define your own product by breaking it into a set of\ncommands - \"stuff that happens\".  That's pretty much it.\n\nIf you're using this module, and need help, you can:\n\n- Post a [github issue][Issue],\n- Tweet to [@senecajs][Tweet],\n- Ask on the [Gitter][Gitter].\n\nIf you are new to Seneca in general, please take a look at [senecajs.org][Org]. We have\neverything from tutorials to sample apps to help get you up and running quickly.\n\nSeneca's source can be read in an annotated fashion by running `npm run annotate`. An\nannotated version of each file will be generated in `./docs/`.\n\n## Install\nTo install via npm,\n\n```\nnpm install seneca\n```\n\n## Quick Example\n\n```js\n'use strict'\n\nvar Seneca = require('seneca')\n\n\n// Functionality in seneca is composed into simple\n// plugins that can be loaded into seneca instances.\n\n\nfunction rejector () {\n  this.add('cmd:run', (msg, done) =\u003e {\n    return done(null, {tag: 'rejector'})\n  })\n}\n\nfunction approver () {\n  this.add('cmd:run', (msg, done) =\u003e {\n    return done(null, {tag: 'approver'})\n  })\n}\n\nfunction local () {\n  this.add('cmd:run', function (msg, done) {\n    this.prior(msg, (err, reply) =\u003e {\n      return done(null, {tag: reply ? reply.tag : 'local'})\n    })\n  })\n}\n\n\n// Services can listen for messages using a variety of\n// transports. In process and http are included by default.\n\n\nSeneca()\n  .use(approver)\n  .listen({type: 'http', port: '8260', pin: 'cmd:*'})\n\nSeneca()\n  .use(rejector)\n  .listen(8270)\n\n\n// Load order is important, messages can be routed\n// to other services or handled locally. Pins are\n// basically filters over messages\n\n\nfunction handler (err, reply) {\n  console.log(err, reply)\n}\n\nSeneca()\n  .use(local)\n  .act('cmd:run', handler)\n\nSeneca()\n  .client({port: 8270, pin: 'cmd:run'})\n  .client({port: 8260, pin: 'cmd:run'})\n  .use(local)\n  .act('cmd:run', handler)\n\nSeneca()\n  .client({port: 8260, pin: 'cmd:run'})\n  .client({port: 8270, pin: 'cmd:run'})\n  .use(local)\n  .act('cmd:run', handler)\n\n\n// Output\n// null { tag: 'local' }\n// null { tag: 'approver' }\n// null { tag: 'rejector' }\n```\n\n\n## Running\n\nTo run normally, say in a container, use\n\n```sh\n$ node microservice.js\n```\n\n(where `microservice.js` is a script file that uses Seneca).\nLogs are output in JSON format so you can send them to a logging service.\n\nTo run in test mode, with human-readable, full debug logs, use:\n\n```\n$ node microservice.js --seneca.test\n```\n\n\n## Why we built this?\n\nSo that it doesn't matter,\n\n   * __who__ _provides_ the functionality,\n   * __where__ it _lives_ (on the network),\n   * __what__ it _depends_ on,\n   * it's __easy__ to _define blocks of functionality_ (plugins!).\n\nSo long as _some_ command can handle a given JSON document, you're good.\n\nHere's an example:\n\n```javascript\nvar seneca = require('seneca')()\n\nseneca.add({cmd: 'salestax'}, function (msg, done) {\n  var rate  = 0.23\n  var total = msg.net * (1 + rate)\n  done(null, {total: total})\n})\n\nseneca.act({cmd: 'salestax', net: 100}, function (err, result) {\n  console.log(result.total)\n})\n```\n\nIn this code, whenever seneca sees the pattern `{cmd:'salestax'}`, it executes the\nfunction associated with this pattern, which calculates sales tax. There is nothing\nspecial about the property `cmd` . It is simply the property we want to pattern match.\nYou could look for `foo` for all seneca cares! Yah!\n\nThe `seneca.add` method adds a new pattern, and the function to execute whenever that\npattern occurs.\n\nThe `seneca.act` method accepts an object, and runs the command, if any, that matches.\n\nWhere does the sales tax rate come from? Let's try it again:\n\n```js\nseneca.add({cmd: 'config'}, function (msg, done) {\n  var config = {rate: 0.23}\n  var value = config[msg.prop]\n  done(null, {value: value})\n})\n\nseneca.add({cmd: 'salestax'}, function (msg, done) {\n  seneca.act({cmd: 'config', prop: 'rate'}, function (err, result) {\n    var rate  = parseFloat(result.value)\n    var total = msg.net * (1 + rate)\n    done(null, {total: total})\n  })\n})\n\nseneca.act({cmd: 'salestax', net: 100}, function (err, result) {\n  console.log(result.total)\n})\n```\n\nThe `config` command provides you with your configuration. This is cool because it\ndoesn't matter _where_ it gets the configuration from - hard-coded, file system,\ndatabase, network service, whatever. Did you have to define an abstraction API to make\nthis work? Nope.\n\nThere's a little but too much verbosity here, don't you think? Let's fix that:\n\n\n```javascript\nseneca.act('cmd:salestax,net:100', function (err, result) {\n  console.log(result.total)\n})\n```\n\nInstead of providing an object, you can provide a string using an\n[abbreviated form][Jsonic] of JSON. In fact, you\ncan provide both:\n\n```javascript\nseneca.act('cmd:salestax', {net: 100}, function (err, result) {\n  console.log(result.total)\n})\n```\n\nThis is a _very convenient way of combining a pattern and parameter data_.\n\n### Programmer Anarchy\n\nThe way to build Node.js systems, is to build lots of little\nprocesses. Here's a great talk explaining why you should do this:\n[Programmer Anarchy](http://vimeo.com/43690647).\n\nSeneca makes this really easy. Let's put configuration out on the\nnetwork into its own process:\n\n```javascript\nseneca.add({cmd: 'config'}, function (msg, done) {\n  var config = {rate: 0.23}\n  var value = config[msg.prop]\n  done(null, { value: value })\n})\n\nseneca.listen()\n```\n\nThe `listen` method starts a web server that listens for JSON\nmessages. When these arrive, they are submitted to the local Seneca\ninstance, and executed as actions in the normal way.  The result is\nthen returned to the client as the response to the HTTP\nrequest. Seneca can also listen for actions via a message bus.\n\nYour implementation of the configuration code _stays the same_.\n\nThe client code looks like this:\n\n\n```javascript\nseneca.add({cmd: 'salestax'}, function (msg, done) {\n  seneca.act({cmd: 'config', prop: 'rate' }, function (err, result) {\n    var rate  = parseFloat(result.value)\n    var total = msg.net * (1 + rate)\n    done(null, { total: total })\n  })\n})\n\nseneca.client()\n\nseneca.act('cmd:salestax,net:100', function (err, result) {\n  console.log(result.total)\n})\n```\n\nOn the client-side, calling `seneca.client()` means that Seneca will\nsend any actions it cannot match locally out over the network. In this\ncase, the configuration server will match the `cmd:config` pattern and\nreturn the configuration data.\n\nAgain, notice that your sales tax code _does not change_. It does not\nneed to know where the configuration comes from, who provides it, or\nhow.\n\nYou can do this with every command.\n\n### Keeping the Business Happy\n\nThe thing about business requirements is that they have no respect for\ncommon sense, logic or orderly structure. The real world is messy.\n\nIn our example, let's say some countries have single sales tax rate,\nand others have a variable rate, which depends either on locality, or product category.\n\nHere's the code. We'll rip out the configuration code for this example.\n\n```javascript\n// fixed rate\nseneca.add({cmd: 'salestax'}, function (msg, done) {\n  var rate  = 0.23\n  var total = msg.net * (1 + rate)\n  done(null, { total: total })\n})\n\n\n// local rates\nseneca.add({cmd: 'salestax', country: 'US'}, function (msg, done) {\n  var state = {\n    'NY': 0.04,\n    'CA': 0.0625\n    // ...\n  }\n  var rate = state[msg.state]\n  var total = msg.net * (1 + rate)\n  done(null, {total: total})\n})\n\n\n// categories\nseneca.add({ cmd: 'salestax', country: 'IE' }, function (msg, done) {\n  var category = {\n    'top': 0.23,\n    'reduced': 0.135\n    // ...\n  }\n  var rate = category[msg.category]\n  var total = msg.net * (1 + rate)\n  done(null, { total: total })\n})\n\n\nseneca.act('cmd:salestax,net:100,country:DE', function (err, result) {\n  console.log('DE: ' + result.total)\n})\n\nseneca.act('cmd:salestax,net:100,country:US,state:NY', function (err, result) {\n  console.log('US,NY: ' + result.total)\n})\n\nseneca.act('cmd:salestax,net:100,country:IE,category:reduced', function (err, result) {\n  console.log('IE: ' + result.total)\n})\n\n```\n\nIn this case, you provide different implementations for different patterns. This lets you\nisolate complexity into well-defined places. It also means you can deal with special\ncases very easily.\n\n## Contributing\n\nThe [Senecajs org][Org] encourages participation. If you feel you can help in any way, be\nit with bug reporting, documentation, examples, extra testing, or new features feel free\nto [create an issue][Issue], or better yet, [submit a Pull Request][Pull]. For more\ninformation on contribution please see our [Contributing][Contrib] guide.\n\n\n### Test\nTo run tests locally,\n\n```\nnpm run test\n```\n\nTo obtain a coverage report,\n\n```\nnpm run coverage; open docs/coverage.html\n```\n\n\n\n## License\nCopyright (c) 2010-2018 Richard Rodger and other contributors;\nLicensed under __[MIT][Lic]__.\n\n\n\n[BadgeCoveralls]: https://coveralls.io/repos/senecajs/seneca/badge.svg?branch=master\u0026service=github\n[BadgeDeepScan]: https://deepscan.io/api/teams/5016/projects/6816/branches/59148/badge/grade.svg\n[BadgeCodeClimate]: https://api.codeclimate.com/v1/badges/3a95be9ab6432c620bea/maintainability\n[BadgeNpm]: https://badge.fury.io/js/seneca.svg\n[BadgeGitter]: https://badges.gitter.im/senecajs/seneca.svg\n[BadgeNpmFigs]: https://img.shields.io/npm/dm/seneca.svg?maxAge=2592000\n[BadgeTravis]: https://travis-ci.org/senecajs/seneca.svg?branch=master\n[CoC]: http://senecajs.org/code-of-conduct\n[Contrib]: http://senecajs.org/contribute\n[Coveralls]: https://coveralls.io/github/senecajs/seneca?branch=master\n[DeepScan]: https://deepscan.io/dashboard#view=project\u0026tid=5016\u0026pid=6816\u0026bid=59148\n[CodeClimate]: https://codeclimate.com/github/senecajs/seneca/maintainability\n[Gitter]: https://gitter.im/senecajs/seneca\n[Issue]: https://github.com/senecajs/seneca/issues/new\n[Lead]: https://github.com/rjrodger\n[Lic]: ./LICENSE\n[Logo]: http://senecajs.org/files/assets/seneca-logo.jpg\n[Npm]: https://www.npmjs.com/package/seneca\n[Org]: http://senecajs.org/\n[Pull]: https://github.com/senecajs/seneca/pulls\n[Sponsor]: http://www.voxgig.com\n[Travis]: https://travis-ci.org/senecajs/seneca?branch=master\n[Tweet]: https://twitter.com/senecajs\n\n[Jsonic]: https//github.com/rjrodger/jsonic\n[Lightning]: http://aws.amazon.com/message/67457/\n[Plugins]: https://github.com/search?utf8=%E2%9C%93\u0026q=seneca\u0026type=Repositories\u0026ref=searchresults\n[taomicro]: https://bitly.com/rrtaomicro\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsenecajs%2Fseneca","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsenecajs%2Fseneca","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsenecajs%2Fseneca/lists"}