Giter VIP home page Giter VIP logo

postal.request-response's Introduction

postal.request-response

v0.3.1

What is it?

postal.request-response is an add-on for postal.js which gives postal a request/response pattern API alongside the normal messaging (publish/subscribe) API which postal's core supports. A publisher can invoke request instead of publish - this returns a promise which can be used to handle a "success" reply (via the success callback) or a timeout(if you've set one) and/or "error" reply via the error handler.

How do I use it?

This add-on adds a request method to the ChannelDefinition prototype, with a method signature of channel.request(options), where options can contain the following:

  • topic - the topic for the message
  • data - the message data
  • timeout - a timeout value in milliseconds. If the reply does not occur within this threshold, the promise moves to failed/rejected state, invoking the error handler the user passed to .then()
  • envelope - it's possible you may want to customize your envelope. If this is the case, you can pass a full envelope instead of topic and data, and it will be used to build up the message itself. *Important: you should not pass envelope and topic/data, choose one or the other, as the envelope option will be picked over topic/data if it exists.
  • replyTopic - the topic that should be used on the reply. This defaults to the requestId that is generated for this request.
  • replyChannel - the channel name that should be used on the reply. This defaults to postal.request-response, but you can override it to whatever you like.

The request method returns a promise, which you can call then on and pass in success & error handlers. The success handler receives the message data arg. The error handler takes a single err argument.

Enough Talk, Show Me Code

To make a request, you can do the following:

var chn1 = postal.channel("user");

chn1.request({
	topic: "last.login",
	data: { userId: 8675309 },
	timeout: 2000
}).then(
	function(data) {
		console.log("Last login for userId: " + data.userId + " occurred on " + data.time);
	},
	function(err) {
		console.log("Uh oh! Error: " + err);
	}
);

To handle requests:

// SUCCESS REPLY
var subscription = chn1.subscribe("last.login", function(data, envelope) {
	var result = getLoginInfo(data.userId);
    // `reply` uses a node-style callback, with error as the first arg
    // or data (for success) as the second
	envelope.reply(null, { time: result.time, userId: data.userId });
});

// ERROR REPLY
var subscription = chn1.subscribe("last.login", function(data, envelope) {
    var result = getLoginInfo(data.userId);
    // `reply` uses a node-style callback, with error as the first arg
    // or data (for success) as the second
    envelope.reply({ msg: "No such user" });
});

###Wait - What Promise Lib Are You Using?! That's up to you, actually. I have no desire to force another dependency on you. So, you get to pick. The only catch is you need to tell postal how to create a "deferred" instance and a "promise" instance.

By "deferred", I'm referring to the internal instance supported by most major libs that expose methods like resolve, reject, etc. In other words, the instance that has the ability to both observer and change state.

By "promise" instance, I'm referring to the value that you'd hand off to the caller which should have a then method.

Let's look at some examples:

####Using jQuery

// We need to tell postal how to get a deferred instance
postal.configuration.promise.createDeferred = function() {
	return new $.Deferred();
};
// We need to tell postal how to get a "public-facing"/safe promise instance
postal.configuration.promise.getPromise = function(dfd) {
	return dfd.promise();
};

####Using Q (v0.9)

// We need to tell postal how to get a deferred instance
postal.configuration.promise.createDeferred = function() {
	return Q.defer();
};
// We need to tell postal how to get a "public-facing"/safe promise instance
postal.configuration.promise.getPromise = function(dfd) {
	return dfd.promise;
};

####Using when.js

// We need to tell postal how to get a deferred instance
postal.configuration.promise.createDeferred = function() {
	return when.defer();
};
// We need to tell postal how to get a "public-facing"/safe promise instance
postal.configuration.promise.getPromise = function(dfd) {
	return dfd.promise;
};

####Using rsvp

// We need to tell postal how to get a deferred instance
postal.configuration.promise.createDeferred = function() {
	return RSVP.defer();
};
// We need to tell postal how to get a "public-facing"/safe promise instance
postal.configuration.promise.getPromise = function(dfd) {
	return dfd.promise;
};

##How Does It work? This is an add-on for postal.js - which is an in-memory message bus. The core behavior in postal is that publishers publish messages to which any number of susbcribers can listen (and subscribers never need a direct reference to the publisher(s)). These messages are "fire and forget". It's perfectly reasonable to set up your own "request/response" implementation using your own custom topics for both the request and response side of things, etc. In other words - you can achieve "request/response" behavior indirectly through "event/informational" messages, instead of "command/RPC" messages. However, there are times where having the clearly expressed intent of "request/response" is the best fit for the job - that's why I wrote this.

The request method wraps the publish call, and adds some extra fields to the envelope, so a request message may look something like this:

{
    "topic": "last.login",
    "data": {
        "userId": 8675309
    },
    "headers": {
        "replyable": true,
        "requestId": "d76b71be-d8d7-44ac-95d4-1d2f87251715",
        "replyTopic": "d76b71be-d8d7-44ac-95d4-1d2f87251715",
        "replyChannel": "postal.request-response"
    },
    "channel": "channel1",
    "timeStamp": "2014-04-23T04:03:56.814Z"
}

Notice the headers? This request has been given a unique ID (an RFC4122 version 4 compliant GUID). When postal sees this metadata on the envelope, it will add a reply method to the envelope before handing the envelope to the subscriber's callback method. This allows the subscriber a simple way to reply without having to worry about knowing the ID, reply topic and reply channel to use. By default, postal will use the requestId as the topic, and postal.request-reponse as the channel on the reply. An example reply to the above message might look like this:

{
    "channel": "postal.request-response",
    "topic": "d76b71be-d8d7-44ac-95d4-1d2f87251715",
    "headers": {
        "isReply": true,
        "isError": false,
        "requestId": "d76b71be-d8d7-44ac-95d4-1d2f87251715"
    },
    "data": {
        "time": "Wed, 23 Apr 2014 04:03:56 GMT",
        "userId": 8675309
    },
    "timeStamp": "2014-04-23T04:03:56.819Z"
}

WAIT A SECOND, JIM! I thought you said to never ever add behavior to the envelope?

Well - to be clear, I've said never publish behavior (functions). Aside from being an anti-pattern, publishing functions on your messages results in a payload that can't be serialized (to JSON), so the moment you need to send messages across boundaries (to node.js, an iframe, web worker, etc.), then you will have problems!

However, it is OK for postal to manage adding convenience behaviors to an envelope before it hands it to subscriber callbacks if that behavior can be generated dynamically based on envelope data that can be serialized. The above request message is easily serializable, and the remote instance of postal (in an iframe, for example) would handle adding the reply method to the envelope as if the message originated locally in that iframe.

##License? MIT. Go forth.

postal.request-response's People

Contributors

ifandelse 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

postal.request-response's Issues

Uncaught DataCloneError: Failed to execute 'postMessage' on 'Window': An object could not be cloned.

Just as a reference. When used in combination with postal.js(1.0.6), postal.xframe(0.3.2) and postal.federation(0.2.4) the following error is thrown in the default configuration.

Uncaught DataCloneError: Failed to execute 'postMessage' on 'Window': An object could not be cloned. in postal.xframe line 109.

Looks like the .reply method added to the envelope causes this issue.

This can be fixed by setting

postal.fedx.transports.xframe.configure({ safeSerialize: true });

Adding user not working

Hello,

When adding a admin or non-admin user to the portal, the invite mail is not being send (the button Add User keeps spinning after you click it).
This means that users will never be added to the portal.

image

This is not really an issue but don't know where I should put it instead.

It would be nice to have similar features like for example SendinBlue.

  • Able to automatically gain or loose reputation based on SPAM reports or bounced messages (this will make the user be able to send more or less emails per hour).
  • Just like SendGrid and SendinBlue give users their own credentials so they have their own enviroment (don't know if it is already like this because I'm not able to add users).

update dependency on lo-dash and compatible postal version

Thanks for the great plugin, it is very useful! It helped me avoid a much more code heavy tight-augmentation approach. This plugin works with the latest version of postal.js (1.0.2) - any reason the dependency should not be updated? Also, is there really a need to declare a dependency on lo-dash when it's just a pass-through and given postal.js already declares a dependency on lo-dash? Happy to submit a pull request.

Promises on .request and node-style callbacks on .reply is unbalanced

The API is confusing.

Using promises is lovely, but envelope.reply appears to be a node-style callback.

I was actually bitten by this, I expected .reply to simply accept my data and ended up having to dig up where an error Firefox couldn't print was coming from.

I would recommend an alternative to .reply that simply accepts a value, paired with another that accepts an error.

Revising the implementation

@ifandelse Is there anyway the implementation could be changed to the following:

postal.publish({
    channel: 'some.channel',        
    request: {
        topic: 'some.topic',
        data: {some: data},
        timeout: 2000
    },
    response: {
        onSuccess: function (d, e) { }
        onFail: function (err) { }
        always: function (d, e, err) { }
    }
});

The problem I'm having with the current implementation is that it introduces a whole new approach to publishing by having to work with the channel itself. The approach is different, and yet, fundamentally, there's nothing particularly different about request-response.

Also, I think the then should be internalized (but still conform to the configuration for creating promises, of course). Further, onSuccess, onFail, and always conform to the language of the promises domain.

The approach I take above is crystal clear, and we stay within the confines of how we publish otherwise.

It seems to me that it should be easy enough to ingest the object literal above (request, response), and then wrap the channel in request semantics. The response callbacks could internally be mapped to then. So, in effect, the approach I take above becomes sugar on top of the current implementation.

I do like the subscriber side, though (i.e. envelope.reply({ })).

Just some suggestions...

Register one listener at a time

First of all,
thanks for postal.js.

I took a look at postal.request-response code and I couldn't find any constraint related to the number of listeners. I think this number should be limited to one at a time. Do you agree? Did I overlook something?

Correct way to ES6 import this

I'm currently using require('postal.request-response')(require('postal')) to include the module but this seems to fail with webpack for some reason.

So excuse me if this is obvious but what's the way to use ES6 import with this? I can't seem to find the right combination so that postal itself is included.

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.