README
A toolkit which provides a strongly-typed message mapping to internal service calls.
What's the point?
The idea behind post-office is to allow application developers to work with strongly-typed messages and focus on service-level implementation code. Post Office was inspired by the idea of Data Transfer Objects.
For example, consider a service to save a User. A current implementation with no service layer may look like this:
app.post('/users', function(req, res, next) {
// validation logic...
// sanitization logic
// Begin "Service-ish Code"
User
.exists(req.body.email, function(err, exists) {
if (err) {
return next(err);
}
if (exists) {
return next(new Error('User exists'));
}
User
.create({
name: req.body.name,
email: req.body.email,
address: req.body.address
})
.save(function(err, user) {
if (err) {
return next(err);
}
res.status(201).json({user:user});
});
});
});
There are a lot of reasons why this code is less than optimal. For one, this is far from unit-testable. Another is the mixing of different logics such as parsing, validation, and sanitization. Finally, any changes to what should be returned for a user need to be reimplemented on every method that returns a user. The issues quickly become apparent as the project grows in size.
Ideally, I should be able to break down my service code into a very simple collection of methods. This is where the idea of message-based design really shines. In message-based design, you write services that will accept a plain object, and will return some kind of plain object. This object will be verified to look a certain way before entering the service layer, and will be verified to look a certain way when leaving the service layer. Here is an example of the same scenario implemented by a user service with message-based design:
// Example User Service
exports.saveUser = function(msg) {
return User
.exists(msg.email)
.then(function(exists) {
if (exists) {
throw new Error('User exists');
}
})
.then(function() {
return User.create({
name: msg.name,
email: msg.email,
address: msg.address
});
})
.then(function(user) {
return {user: user};
});
};
As you can see: no more req
, res
, or next
. Testing this code is much more straight forward. Errors are simple. We just worry about the business logic, and we let some magic do the rest (mapping, validation, error handling, etc). Post Office aims to be some magic. It is a toolkit that allows you to build "strongly-typed" messages (it's javascript so "strongly-typed" has a somewhat different meaning here) for your services.
Quick Example
var app = require('express')();
var postOffice = require('post-office');
var PropTypes = postOffice.PropTypes;
// 1. create a container
var c = postOffice.createContainer();
// 2. make request envelope
var saveUserEnv = postOffice.createEnvelope({
name: PropTypes.string,
email: PropTypes.string,
favoriteNums: [PropTypes.int]
});
// 3. make response envelope
var saveUserResponseEnv = postOffice.createEnvelope({
name: PropTypes.STRING,
email: PropTypes.STRING,
favoriteNums: [PropTypes.INT],
updatedBy:PropTypes.INT
});
// 4. implement service code
var saveUser = function(msg) {
// do some magic to save user
return Promise.resolve({
name: msg.name,
email: msg.email,
id: 1,
updatedBy: 12345
});
};
// 5. tie it together and build expressjs handler
app.post('/users', c(saveUserEnv, saveUser, saveUserResponseEnv).status(201).json());
/**
* POST /users { "name": "Tyler", "email": "tyler@goguardian.com", "favoriteNums": [22, "33"] }
*
* Response: { "name": "Tyler", "id": 1, "email": "tyler@goguardian.com", "favoriteNums": [22, 33], "updatedBy": 12345 }
*/
Documentation
Learn more on the wiki.