Skip to content

Instantly share code, notes, and snippets.

@tjstebbing
Last active August 29, 2015 14:04
Show Gist options
  • Save tjstebbing/d7cfe81d12f1dde7b868 to your computer and use it in GitHub Desktop.
Save tjstebbing/d7cfe81d12f1dde7b868 to your computer and use it in GitHub Desktop.
Concepts for HT
# Hudson Taylor
Hudson Taylor (HT) is a library for building software systems with a Service
Oriented Architecture. It comprises of server library for providing services
with well documented and defined APIs, and a client library for calling
services. All service APIs have Schemas that both document expectations as
well as validate and pre-process incoming data.
HT can be used within a single process to logically partition services from
the beginning. This means that your project has clean internal interfaces and
when it comes time to scale out, you can break-out services and replicate them
horizontally.
## Server quick-start:
### index.js
```javascript
var ht = require("hudson-taylor");
var myServiceSetup = require("./echo").setup; // Import your service setup.
var s = ht.Service();
s.add("myEchoService", myServiceSetup);
s.listenHTTP({port : 7001}); // Start the service listening via HTTP.
```
### echo.js
```javascript
exports.setup = function(s, readyCallback) {
/* service setup functions take a service object and a callback, You must
* call the callback as soon as your service is ready to receive jobs.
*/
readyCallback(); // No async setup, callback immediately.
//Implement an echo API schema with documentation:
var echoSchema = {
_doc : "Echo API, returns string input that you send to it.",
input : s.String({
maxLength : 200,
_doc : "Input string to be returned" }),
}
s.on("echo", schema, function(data, callback) {
callback(null, data.input); // Data returned must be encodable as JSON
});
//Other API handlers here..
}
```
## Client quick-start:
This connects to our echo service via HTTP and logs a returned string.
```javascript
var s = ht.Services();
s.connect("myEchoService", ht.HTTPClient("http://auth.localhost", 7001));
s.remote("myEchoService", "echo", {input : "Hello World!", function(err, res) {
if(!err) return console.error(err);
console.log(res);
});
```
# The details:
## Methods of communicating between services:
One of the nice things about HT is that you can connect to a service via several
different methods without changing your service code in the slightest.
This can make deployment very flexible, here are some of the communication
methods:
''' server method / client method '''
* listenHTTP / HTTPClient: uses JSON-RPC over HTTP to communicate.
* listenHTTP / HTTPRoundRobinClient: uses JSON-RPC over HTTP to communicate
with multiple instances of a service.
* listenTCP / TCPClient: uses JSON-RPC over TCP to communicate.
* N/A / LocalServiceClient: Host a service natively in process and use JSON
and local function calls to communicate. This is very useful for development,
testing environments or small deployments.
Custom server and client channels can be implemented easily.
# Building Services
## service setup:
The easiest way to get started writing services is to export a setup function for each service, and then
pass those to ht.Services().add(). Your service setup function will register one or more handlers using
service.on() (see below). The signature for a service setup function is:
```javascript
function(<service>, readyCallback, /* any extra arguments passed to ht.Services.add() */)
```
The extra arguments to add() allow you to pass in other services, database connections, logging etc that you
can use within your signal handlers, for instance here is a contrived authentication API that calls out to a
'session' service to check an auth token before continuing with regular authentication:
### index.js
```javascript
authSetup = require("./auth").setup;
var db, logger, services = null;
/* Do some db setup, configure client services, then: */
services.add("auth", authSetup, db, logger, services);
```
And then in your setup:
```javascript
exports.setup = function(s, ready, db, logger, otherService) {
s.on("login", <schema>, function(data, callback) {
// Check if we've got a valid oAuth token
services.remote("session", "checkToken", {token : data.token}, function(err, sessionCheck) {
if(err) return callback(err);
if(!sessionCheck.authenticated) {
//user needs to authenticate again
db.users.find({id : data.userID}, function(err, user) {
//do some checks
callback(authenticated);
});
});
});
})
## service.on("signal", <schema>, handler(data, callback), /* optional */ validationErrorHandler(err, callback))
## Schemas
```javascript
var schema = s.Obj({
_doc : "Echo API, returns string input that you send to it.",
input : s.String({
maxLength : 200,
_doc : "Input string to be returned" }),
})
```
Each schema type is an object with a 'validate' method that will attempt to validate the object
and return the new data, or alternatively raise an error. These can be used to do type conversion
if required. They also have a 'document' method which is called when generating API documentation.
There are several built-in schema types and you can easily implement your own. This can be useful
for doing automatic coversion of application specific types, for instance converting an 'id' string
into a mongoDB ObjectID.
## Built-in Schema types:
### s.Int
### s.Float
### s.Str
### s.Array
### s.Obj
props : { }
### s.Bool
### s.Time
### s.DateTime
### s.and
### s.or
## Common attributes:
### _doc <markdown string>
### _docFile <markdown file>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment