-
transport layer
-
transaction layer
-
encapsulated configuration layer (aka Resource/DataSource)
-
Transport layer
Goals: encapsulate one method of pipelining data with as little code and as few requirements as possible.
var nativeObject = Y.io.<transport>(source, callback(err, response) {
/* 'this' is appropriate object */
});
...optionally support context override as third arg to transport function, and pass appropriate object as third arg to callback
###Examples
var scriptNode = Y.io.script(url, function (err, response) {
/* 'this' is the <script>? */
});
var xhrObj = Y.io.xhr(url, function (err, response) {
/* 'this' is the XHR object?
*/ });
var func = Y.io.func(this.someMethod, function (err, resp) {
/* 'this' is ??? */ },
this); // override context?
- xhr
- xdr
- form (iframe?)
- script
- link
- flash?
- socket
- function/func
- array
- object -- see schema for usefulness
- more...
- Transactions
Goals: create encapsulation of lifecycle wrapping transport, standardize API abstraction over transport layer, add events for consistent transaction lifecycle stages plus any stages that are appropriate per transport, support post-response processing
var transaction = Y.io(source, {
transport: <transport name>,
type: <post-processor name>,
on: {
// or start, end?
send: callback(e) { /* 'this' and e.transaction are the transaction */ },
response: callback(e) { /* same, plus e.response is the transport response */ },
}
after?
context?
args?
});
var transaction = Y.io(source, callback(e) { /* e.response */ } [, context?]);
var transaction = Y.io(source, { config, but without on: {...} }, callback(e) { /* e.response */ } [, context?]);
var transaction = Y.io(source, <type>, callback(e) { /* e.response */ });
Other configuration attributes/properties interpreted by the transport/type modules or generic feature modules/plugins, such as:
method: ('get', 'post', 'put', 'delete', 'head')
multipart: true || separatorString?
sync: false
headers: { 'content-type': 'application/json' }
queuing: true
polling: true || msInterval || { more config? }
disabledFields: false
native: true
- for xdr transportdata: ...
- form data or maybe form Node or idtimeout: ms
schema
- e.g.
schema: {
resultListLocator: 'records.here',
resultFields: [ ... ]
output: ('array', 'arraylist', ModelList, MyArrayListSubclass, more?)
}
on: {
success: callback(e) { ... },
failure: callback(e) { ... }
}
Y.io will default transport based on a test function on each transport, and/or set by the 'type' configuration.
CAVEAT: transport test-based defaulting could create confusion for JSONP vs XHR
var useAsDefault = Y.io.script.test(source, config); // => true/false/likelihood rating
var transaction Y.io('/service', function (e) {
/* xhr transport passes test, is used */
});
// types can default transport
var transaction = Y.io('http://servi.ce/foo', { type: 'jsonp' }, function (e) {
/* 'script' transport defaulted */
});
// types and transports can be incompatible
var boom = Y.io(source, { transport: 'link', type: 'jsonp' }, ...); // boom === null
- Encapsulated configuration
Goals: Create transaction factories with configured defaults.
Use same/similar API signature to Y.io().
NAMING DISCUSSION: Y.DataSource, Y.Resource, Y.io.Resource, other?
var resourceA = new Y.DataSource(url/data, { ... });
var transaction = resourceA.send(extra/data, {
/* config overrides, plus transaction level... */
on: { ... }
}, contextOverride?);
// Transaction object points back to originating resource
transaction.resource; // => resourceA
// Post-creation subscription possible
transaction.on('success', fn);
transaction.after('end', fn);
// Transport-specific API decoration?
transaction.abort();
- (bonus!) Widget extension API
Goals: normalize API for data layer configuration of Widgets (and others?)
Mirror the DataSource/IO API, but with 'source' property in configuration object taking the place of the leading url/source argument. Widget attribute should be 'data' (debatable).
var table = new Y.DataTable({
data: {
source: url,
type: 'jsonp',
schema: {
resultListLocator: 'records',
...
}
},
columns: [ ... ]
});
table.load( <sig args for dataSourceInstance.send( ... )> );
var chart = new Y.Chart({
data: {
source: url,
type: 'jsonp',
schema: {
resultListLocator: 'records',
...
}
},
type: 'pie'
});
chart.load( <sig args for dataSourceInstance.send( ... )> );
@lsmith, thanks for taking the time to think through all this stuff! I have some comments that I'll group in each of the three sections:
1. Transport Layer
I really like the callback-based, Node.js-like API. This seems like the way to go for sure!
I'm unsure what the context (
this
) inside the callback should be. I think we should consider whatever is standard in Node.js and trade that off with what is standard in YUI.I feel that these APIs should be considered to be async, even the
function
andarray
transports. Do you think it makes sense to strictly enforce that by throwing things likearray
off thread viasetTimeout()
? Or is that crazy?It seems like you are only thinking about these transports as only read-only APIs. When thinking about something like XHR, it should be able to be configured to
POST
,PUT
,DELETE
, etc. To clarify, the API could be defined like:Where
source
is an overloaded argument that each transport can dictate the "shorthand" type, but all transports should accept it as an object with asrc
property as well.Going back to the XHR, a
POST
request could look like this:I think the
Y.io.xhr
function could also have some convenience methods hanging off of it to make it easier to get/send JSON data.2. Transactions
Another goal for the transaction layer is to provide a standalone
Y.Transaction
(or some named thing) class which is based on the concepts of deferreds/promises.I think it will be interesting to see if we end up wanting to build the transaction support on top of the current custom event API, or if we'll end up wanting to do something simpler…
To support your proposed API here, the user would also have to include the correct transport module in their
use()
statement, correct? Or were you thinking that they'd includeio
to get the transaction support, and theio
module would depend on all the transport modules? <-- I don't like that.I don't like the idea of overloading the transaction like you have it in the Transport-specific configuration section. I think the transport object should be a standardized on a single interface. They are an abstraction layer over all the nuances of the transports. In other words, I don't think
Y.io()
should necessarily be a transaction factory, in the sense that you're "configuring" a transaction. I think a standardized transaction object should just be the return value of the function. What you have smells more like it should be named:Y.io.createTransaction(source, options)
.I think the pattern should be one that forces each transport to adhere to the common transaction interface, and the transport encapsulates the implementation of how this contract with the transaction is met.
3. Encapsulated Configuration
So yeah, everything I said in the section above about transactions is going to clash with what you have for this section :P
I think what you have here is definitely overloading the idea of transactions with this. We want to add another level of abstraction, but this time it's around a specific data resource.
Y.DataSource
/Y.Resource
should be a fancy factory function that returns aDataSource
/Resource
instance. That instance object has API methods, that when called, returnY.Transaction
objects.I see the goals of this to be a smart factory that when called, it processes its arguments to determine the which transport to use. It then encapsulates the configuration for that transport and wraps it with a common "resource" API which will call into that transport and return transaction objects.
It makes sense to have
DataSource
/Resource
subclasses that model a desirable API over a specific transport, but also work with a commonDataSource
/Resource
API as well. This aligns well with the idea thatY.DataSource()
is a factory which creates the appropriateDataSource
/Resource
subclass based on the inputs to the function.Another option is to somehow expose the underlying transport's API, but wrap everything to use the configuration defaults and return transaction objects.
AutoComplete
has some of this smart transport parsing, we should make this sort of thing available generically in the library and relieveAutoComplete
of this duty.Some of these ideas for resource encapsulation are coming from my REST Resource Gallery module.