Git Product home page Git Product logo

mu's People

Contributors

mcdonnelldean avatar mcollina avatar pelger avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mu's Issues

mu v seneca transport

Im new to the scene and trying to understand. Can someone compare and contrast mv and senecajs transport?

timeout logic

we need to clearly define timeout logic, and provide options to configure

don't JSON.stringify log objects

I'm seeing this a lot:

    mu.log.debug('node: ' + muid + ' <- ' + JSON.stringify(msg))

That's going to JSON.stringify every time, even if the logging level is above debug

Instead let pino deal with it

    mu.log.debug('node: %s <- %j', muid, msg)

This way the object will only be (safely) stringified if logging is on

remoteStack bug

Question: Why I have two error objects in the remoteStack which point to the same error in the service c ?

I create following scenario:

service-a

const mu = require('mu')({
  dev: process.NODE_ENV !== 'production'
})
const tcp = require('mu-tcp')

// define routing:

mu.inbound({
  role: 'some'
}, tcp.server({
  port: 3000,
  host: '127.0.0.1'
}))

mu.outbound({
  role: 'other'
}, tcp.client({
  port: 3001,
  host: '127.0.0.1'
}))

// define patterns:

mu.define({
  role: 'some',
  cmd: 'sub'
}, function (args, cb) {

  mu.dispatch({
    role: 'other',
    cmd: 'sub',
    a: 1,
    b: 2
  }, function (err, result) {

    if (err) {
      return cb(err);
    }

    cb(null, result);
  })
})

service b

const mu = require('mu')({dev: process.NODE_ENV !== 'production'})
const tcp = require('mu-tcp')

// define routing:

mu.outbound({role: 'some'}, tcp.client({port: 3000, host: '127.0.0.1'}))

// define patterns:

mu.dispatch({ role: 'some', cmd: 'sub', a: 1, b: 2 }, function (err, result) {
  
  console.log(JSON.stringify(err))
})

service c

const mu = require('mu')({
  dev: process.NODE_ENV !== 'production'
})
const tcp = require('mu-tcp')

// define routing:

mu.inbound({
  role: 'other'
}, tcp.server({
  port: 3001,
  host: '127.0.0.1'
}))

// define patterns:

mu.define({
  role: 'other',
  cmd: 'sub'
}, function (args, cb) {
  
  cb(mu.error.wrapRemote(mu.error('invalid'), 1, 500, { msg: 'jesus' }))
  
})

Steps to reproduce

  1. Start service c
  2. Start service a
  3. Start service b

I got following error payload

{
  "data": null,
  "isBoom": true,
  "isServer": true,
  "output": {
    "statusCode": 500,
    "payload": {
      "statusCode": 500,
      "error": "Internal Server Error",
      "message": "An internal server error occurred",
      "mu": {
        "code": 1,
        "error": "service error",
        "message": "invalid"
      }
    },
    "headers": {},
    "mu": {
      "code": 1,
      "error": "service error",
      "message": "invalid"
    }
  },
  "isMu": true,
  "remoteStacks": [{
        "timestamp": 1478711395108,
        "stack": "Error: invalid\n    at makeMuError (E:\\Repositorys\\mu-test\\node_modules\\mu-error\\index.js:56:21)\n    at monoMorphMuError (E:\\Repositorys\\mu-test\\node_modules\\mu-error\\index.js:188:14)\n    at Object.mue [as error] (E:\\Repositorys\\mu-test\\node_modules\\mu-error\\index.js:49:14)\n    at Object.tf (E:\\Repositorys\\mu-test\\service-c.js:22:29)\n    at Object.route (E:\\Repositorys\\mu-test\\node_modules\\mu-router\\index.js:71:14)\n    at Object.dispatch (E:\\Repositorys\\mu-test\\node_modules\\mu\\index.js:87:12)\n    at receive (E:\\Repositorys\\mu-test\\node_modules\\mu-transport\\index.js:56:8)\n    at DestroyableTransform._transform (E:\\Repositorys\\mu-test\\node_modules\\mu-tcp\\driver.js:87:11)\n    at DestroyableTransform.Transform._read (E:\\Repositorys\\mu-test\\node_modules\\readable-stream\\lib\\_stream_transform.js:159:10)\n
        at DestroyableTransform.Transform._write(E: \\Repositorys\\ mu - test\\ node_modules\\ readable - stream\\ lib\\ _stream_transform.js: 147: 83)
        "},{"
        timestamp ":1478711395110,"
        stack ":"
        Error: invalid\ n at Error(native)\ n at Function.wrapRemote(E: \\Repositorys\\ mu - test\\ node_modules\\ mu - error\\ index.js: 132: 19)\ n
        at Object.tf(E: \\Repositorys\\ mu - test\\ service - c.js: 22: 15)\ n at Object.route(E: \\Repositorys\\ mu - test\\ node_modules\\ mu - router\\ index.js: 71: 14)\ n at Object.dispatch(E: \\Repositorys\\ mu - test\\ node_modules\\ mu\\ index.js: 87: 12)\ n at receive(E: \\Repositorys\\ mu - test\\ node_modules\\ mu - transport\\ index.js: 56: 8)\ n at DestroyableTransform._transform(E: \\Repositorys\\ mu - test\\ node_modules\\ mu - tcp\\ driver.js: 87: 11)\ n at DestroyableTransform.Transform._read(E: \\Repositorys\\ mu - test\\ node_modules\\ readable - stream\\ lib\\ _stream_transform.js: 159: 10)\ n at DestroyableTransform.Transform._write(E: \\Repositorys\\ mu - test\\ node_modules\\ readable - stream\\ lib\\ _stream_transform.js: 147: 83)\ n at doWrite(E: \\Repositorys\\ mu - test\\ node_modules\\ readable - stream\\ lib\\ _stream_writable.js: 313: 64)
        "}],"
        message ":"
        invalid ","
        stack ":"
        Error: invalid\ n at Error(native)\ n at Function.wrapRemote(E: \\Repositorys\\ mu - test\\ node_modules\\ mu - error\\ index.js: 132: 19)\ n at Object.route(E: \\Repositorys\\ mu - test\\ node_modules\\ mu - router\\ index.js: 111: 30)\ n at Object.dispatch(E: \\Repositorys\\ mu - test\\ node_modules\\ mu\\ index.js: 87: 12)\ n at receive(E: \\Repositorys\\ mu - test\\ node_modules\\ mu - transport\\ index.js: 56: 8)\ n
        at null. < anonymous > (E: \\Repositorys\\ mu - test\\ node_modules\\ mu - tcp\\ driver.js: 59: 11)\ n at emitOne(events.js: 77: 13)\ n at emit(events.js: 169: 7)\ n
        at readableAddChunk(E: \\Repositorys\\ mu - test\\ node_modules\\ readable - stream\\ lib\\ _stream_readable.js: 198: 18)\ n at Readable.push(E: \\Repositorys\\ mu - test\\ node_modules\\ readable - stream\\ lib\\ _stream_readable.js: 157: 10)
        "}

callback in tearDown

server.close() has a callback, this is important for tests when you want to do mu.tearDown(() => t.end())

if you do t.end() outside of cb, test ends before server closes

self documenting... / doc-generation

  1. it should be a hard requirement that patterns are registered at initialization (as far as I can tell there's no reason for lazy initialization of patterns)
  2. since this is the case, we can run the process with an instrumented mu (proxyquire), which could allow us to grab all patterns and functions
  3. we can use these patterns (and functions) to generate api doc template

websocket transport

Should be fairly straightforward to implement, one catch though - have alt browser client only incarnation that doesnt use streams

modification to mu API

Hey @davidmarkclements, @mcollina,
Just reviewing the mu code so wanted to flag this one. It seems to me that the API is a little inconsistent. namely mu.define receives the entire message (pattern and protocol) whilst the final callback handler receives err and result. IMO the following might be better:

mu.define({...}, function(args, cb, msg)

where args = msg.pattern
cb as before
msg = full message (i.e. pattern + protocol)

mu.dispatch({...}, function(err, result, msg)

where result = msg.response
msg = full message (i.e. response + protocol)

this way the msg can be ignored in most cases but is available for inspection if required.

Thoughts?

streams interopability

integration with streams (Node streams and pull-streams) should be a core concept, and streaming API's should be first class citizens.

I think the earlier we can deal with this the better

mu.define.stream({role: 'realtime-thing', cmd: 'process-data'}, function (args, stream) {
  // no cb, errors handled on stream itself
  stream.pipe(createTransformFrom(args)).pipe(stream) // stream is a duplex, writable end is response hooked into transport
})

// elsewhere:

var stream = mu.dispatch.stream({role: 'realtime-thing', cmd: 'process-data'}) // no cb, events handled on stream (also maybe client should (optionally?) return pull-stream)
stream.on('error', handleIt)
stream.write('some data YOOOOO')
stream.write('Its all realtime in here init')
if (thereIsAProblem) { 
  stream.emit('error', mu.error('AHHHHHHH noooo'))
} else {
  stream.end('yay')
}

If a transport doesn't yet or can't support streaming, we can simply throw immediately when define.stream or dispatch.stream is called

cc @mcollina @pelger @mcdonnelldean


I think perhaps an explicit API is easier/cleaner from a user and implementation POV, than embedding streams in patterns i.e.:

mu.define({role: 'realtime-thing', cmd: 'process-data'}, function (args, cb) {
   if (invalid(args) { return cb(mu.error('invalid args for some reason')) }
   cb(null, {stream: args.data.pipe(someTransform)})
})

mu.dispatch({role: 'realtime-thing', cmd: 'process-data', data: myStream}, function (err, res) {
    if (err) { return handle(err) }
    // callback signifies that args were handled, and stream was instantiated
    var stream = res.stream
    stream.on('error', handleStreamErr)
    stream.pipe(someWhereElse)
})

tearDown should accept a callback

mu/core/core.js

Lines 85 to 87 in caa0443

function tearDown () {
router.tearDown()
}

mu/core/router.js

Lines 139 to 147 in caa0443

var tearDown = function tearDown () {
patrun.list().forEach(function (el) {
if (el.data && el.data.tearDown) {
el.data.tearDown()
}
})
if (defaultTf && defaultTf.tearDown) {
defaultTf.tearDown()
}

Something like https://github.com/mcollina/fastparallel, https://github.com/mcollina/steed or https://github.com/caolan/async needs to be used.

some kind of fallback + copy adapter

prime use case is hydrating local state from a remote source (particularly in a web app scenario) then replay from local

mu.outbound('*', hydrate(local(), ws.client(opts)))

@pelger @mcollina is there an analogous term for this (as with tee and balance)

make the pattern the primary object for args

since accessing the pattern is definitely the main case, what if we make it the
args object, and make protocol accessible either through an additional parameter or on the proto of the args object. From the transport, given we have an object (o) that is composed {pattern, protocol} we can do

var args = o.pattern
pattern.__proto__ = {protocol: o.protocol}

Then we would use like so

mu.define({role: 'some', cmd: 'thing'}, function (args, cb) {
   console.log(args.role === 'some') // true
   console.log(args.protocol.ttl) // 10
})

since protocol is on the proto, it won't show up during serialization (or logging, but we can make a pino serializer)

The other approach would be

mu.define({role: 'some', cmd: 'thing'}, function (args, cb, protocol) {
   console.log(args.role === 'some') // true
   console.log(protocol.ttl) // 10
})

Or we could go polymorphic with (args, cb) and (args, protocol, cb) being appropriately handled

cc @pelger @mcollina

use es2040 for browserify transform and linting

https://www.npmjs.com/package/es2040

This enforces a subset of useful ES6 features and prevents the others by matter of course

Transpiles this es6 subset for older browsers (obviously faster because it's more focused)

No need for a server side transpile step - all of es2040 is supported in v6

It could be useful to figure out a way to make it a secondary linter (after standard) - maybe start with something simple as attempting to transpile and then "oops that's not es2040"

ES2040 supported features:

  • fat arrows - make inline functions cute-looking
  • template strings / tagged templates - enable DSLs inside of JS
  • const - using const by default makes it easy to spot where values are
    being redeclared
  • object destructuring - const { a, b } = { a: 1, b: 2 }
  • array destructuring - const [a, b] = [1, 2]
  • default parameters - ({ a = 1, b = 2 } = {}) => a
  • rest parameters - (a, b, ...args) => {}
  • spread literals - f(a, b, ...args)
  • short-hand properties - return { a, b }
  • computed properties - return { [a]: b }

If we want to be even more strict, we could go with ES2020 is:

  • fat arrows - make inline functions cute-looking
  • template strings / tagged templates - enable DSLs inside of JS
  • const - using const by default makes it easy to spot where values are
    being redeclared

Which would be probably be wicked fast at transpiling.

In either case, much faster than babel.

Thoughts? @mcdonnelldean @lucamaraschi @mcdonnelldean @mcollina

unassertify and unassert-cli

I like that we're going with the assert approach (apparently known as design by contract)

I think we should go all out with assert and just assert the crap out of everything

And then "unassert" strip all asserts (in a production server build and browserify build) using http://npm.im/unassert-cli and http://npm.im/unassertify

The only caveat I can think of, is care should be taken when defensive programming is required for production scenarios - in which case don't use assert, use an if statement

support func/local transport on same instance

currently for in-process we have to do

var func = require('mu/drivers/func')
var createMu = require('mu')
var mu1 = createMu()
var mu2 = createMu()
mu1.inbound('*', func())
mu2.outbound('*', func({target: mu1})

I think this should be simpler, particularly for the browser case

var func = require('mu/drivers/func')
var mu = require('mu')()
mu.inbound('*', func())
mu.outbound('*', func({target: mu})) // currently causes max stack error on dispatch

or preferably, no need to specify target:

var func = require('mu/drivers/func')
var mu = require('mu')()
mu.inbound('*', func())
mu.outbound('*', func())

This allows for something like

var func = require('mu/drivers/func')
var http = require('mu/drivers/http')
var mu = require('mu')()
mu.inbound('*', http(opts))
mu.inbound({role: 'local-state'}, func())
mu.outbound({role: 'local-state'}, func())

Passing error details on a wrapRemote ?

I just play something with the remote errors and I could not find a possibility to transfer more than just the timestamp and stack to the previous callee. I think those informations are good for logging but if you want to handle business cases it would be good to know what went wrong.

It would be nice to add also the message object to the stack item.

mu.error.wrapRemote(error, muCode, statusCode, message)

review for browser usage

from a brief look its clear some pieces of mu are not browser friendly

for instance, requiring the entire lodash library to use _.isString when you can simply use typeof and _.cloneDeepwhen you can (and should) useObject.assign (polyfill for older browsers)

This would significantly reduce browser side payload

I think we should review the whole for browser side usage and keep the browser in mind
The first step being, no lodash.

Error handling

Hi
why has a failed dispatch action the field err in the result payload?

function(err, result) {

}

err = <error>
result = { err: <error> }

if you pass an error and a result the payload looks like

result: { err: <error>, <additional-result-> }

community friendly driver boilerplate

the drivers look like C code wrapped in a JS closure (for instance, a scoped options object implicitly used in a listen function instead of being passed into listen)

if we rewrite the drivers in more typical js style - we can derive boilerplate that can be used for third part drivers to encourage ecosystem growth

Roadmap

Hi @davidmarkclements @mcollina I think we should create a roadmap to work on the right things.
At the moment there are lots of ideas. We should focus on the minimum to make it operational.

I suggest

  • Writing down the api interface

  • Implement a logging interface

  • Clear concept of error handling + implementation

  • Implement an interface for payload validation

  • Make HTTP/TCP Transports stable.

Add Bloomrun over Patrun

Bloomrun is far faster (3x - 10x) than Patrun, it also supports index & depth first (which are equally valid).

The only concern I have right now is mu uses * over \*\ for regex. This means we either,

  • Change mu to do the conversion
  • Change bloomrun to do the conversion

@mcollina thoughts on this?

mu-tcp - graceful disconnect

currently, a service will log an error (premature close) when a client disconnects from server

handle this gracefully

Abstract to remain resilient to failures

Mu is an message based router for building distributed systems this include a concept which describes how to dealing with failure scenerios.

Netflix use this Approach I think we can get inspired to implement a simliar strategy for Mu.

Now, we can start the discussion.

References:
https://github.com/Netflix/Hystrix/wiki/Operations
http://techblog.netflix.com/2012/02/fault-tolerance-in-high-volume.html
https://msdn.microsoft.com/en-us/library/dn589784.aspx
https://github.com/Netflix/Hystrix/wiki/

what's msg.pattern.__err about and is there a better way?

It's possible that we do need some way of injecting errors into patterns (although I'd like to validate that thought, to be sure)
but shouldn't errors be on a meta data object (as agreed), rather than going down the "super private" double underscore route?

rename func to local

soon we'll be going to monorepo - transports will be published to npm, so mu/drivers/tcp will be mu-tcp, mu/drivers/http would be mu-http

mu-func sounds like ambigious mu-local is clearer

(for these main cases, I would advocate not adding -driver or -transport to the end of each)

@mcollina @pelger

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.