Skip to content

Commit eea9b2d

Browse files
committed
init checkin.
1 parent 674739d commit eea9b2d

16 files changed

+423
-3
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ logs
1212
results
1313

1414
npm-debug.log
15+
16+
node_modules/

README.md

+46-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,47 @@
1-
state-machine
2-
=============
1+
# State machine as a service
32

4-
State machine as a service
3+
Applications sometimes find themselves managing a process flow comprising of many process states and decisions that move the process along from one state to another. For example, consider a user registration process. When the application creates a new user, that user may start off in a *UNVERIFIED* state. When the application verifies the user's email address, the user may move to a *VERIFIED* state. On the other hand, if sufficient time passes and the user is still in the *UNVERIFIED* state, the application may choose to move the user to a *EXPIRED* state.
4+
5+
Applications with such needs will end up implementing a state machine to manage the process flow. This service aims to save applications from that implementation effort. Using this service your application need only define the state machine that reflects the process flow and then tell the service each time a decision is made in the process. The service will take care of transitioning to the next state in the flow. Furthermore, the application can store arbitraty data along with each state.
6+
7+
# Usage
8+
The service is currently provided as a REST API.
9+
10+
## REST API
11+
12+
### Resources
13+
14+
#### State Machines
15+
16+
##### Create a new state machine
17+
18+
###### Request
19+
20+
POST {baseuri}/v1/state-machines
21+
22+
{
23+
"initialState": "foo",
24+
"states": {
25+
"foo": {
26+
"transitions": [
27+
{ "input": "/1/","nextState": "bar" },
28+
{ "input": "/2/","nextState": "baz" }
29+
]
30+
},
31+
"bar": {
32+
"transitions": []
33+
},
34+
"baz": {}
35+
}
36+
}
37+
38+
###### Response (upon successful creation)
39+
40+
201 Created
41+
Location: {baseuri}/v1/state-machine/2345
42+
43+
{
44+
"links": {
45+
"self": "{baseuri}/v1/state-machine/2345"
46+
}
47+
}

bin/update_db_schema.js

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
var config = require('config'),
2+
childProcess = require('child_process'),
3+
os = require('os'),
4+
fs = require('fs')
5+
6+
var dbConfig = {
7+
dev: {
8+
driver: 'pg',
9+
user: config.db.username,
10+
password: config.db.password,
11+
host: config.db.host,
12+
database: config.db.name
13+
}
14+
}
15+
16+
var dbConfigTempFilePath = os.tmpdir() + '/database.json'
17+
fs.writeFile(dbConfigTempFilePath, JSON.stringify(dbConfig), function(err) {
18+
19+
if (err) {
20+
console.error('Could not create database config file. Aborting.')
21+
process.exit(1)
22+
}
23+
24+
var cmd = 'node_modules/db-migrate/bin/db-migrate --config ' + dbConfigTempFilePath + ' up'
25+
childProcess.exec(cmd, function(err, stdout, stderr) {
26+
27+
console.log(stdout)
28+
if (err) {
29+
console.error(stderr)
30+
}
31+
32+
fs.unlinkSync(dbConfigTempFilePath)
33+
34+
})
35+
36+
})

config/default.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
var matches = []
2+
if (process.env.DATABASE_URL) {
3+
4+
var dbUrlRegex = /([^:]+):\/\/([^:]+):([^@]+)@([^:]+):([\d]+)\/(.+)/
5+
matches = process.env.DATABASE_URL.match(dbUrlRegex)
6+
7+
}
8+
9+
module.exports = {
10+
db: {
11+
dialect: matches[1] || 'postgres',
12+
name: matches[6] || 'statemachine',
13+
username: matches[2] || 'statemachine',
14+
password: matches[3] || '',
15+
host: matches[4] || 'localhost',
16+
port: matches[5] || 5432,
17+
logging: console.log
18+
},
19+
restapi: {
20+
port: process.env.PORT || 8080,
21+
baseuri: 'http://localhost:8080'
22+
}
23+
}

config/runtime.json

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

lib/db.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
var config = require('config'),
2+
sequelize = require('sequelize')
3+
4+
module.exports = new sequelize(config.db.name, config.db.username, config.db.password, {
5+
host: config.db.host,
6+
port: config.db.port,
7+
dialect: config.db.dialect,
8+
omitNull: true,
9+
define: {
10+
syncOnAssociation: false,
11+
underscored: true
12+
},
13+
logging: config.db.logging
14+
})

migrations/20130425004809-init.js

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
var dbm = require('db-migrate'),
2+
async = require('async');
3+
var type = dbm.dataType;
4+
5+
exports.up = function(db, callback) {
6+
async.series([
7+
db.createTable.bind(db, 'state_machines', {
8+
id: { type: 'int', primaryKey: true, autoIncrement: true },
9+
current_state_id: { type: 'int' },
10+
created_at: 'datetime',
11+
updated_at: 'datetime'
12+
}),
13+
db.createTable.bind(db, 'states', {
14+
id: { type: 'int', primaryKey: true, autoIncrement: true },
15+
name: { type: 'string', notNull: true },
16+
state_machine_id: { type: 'int', notNull: true },
17+
data: { type: 'text' },
18+
created_at: 'datetime',
19+
updated_at: 'datetime'
20+
}),
21+
db.createTable.bind(db, 'transitions', {
22+
id: { type: 'int', primaryKey: true, autoIncrement: true },
23+
state_id: { type: 'int', notNull: true },
24+
input: { type: 'string', notNull: true },
25+
next_state_id: { type: 'int', notNull: true },
26+
created_at: 'datetime',
27+
updated_at: 'datetime'
28+
}),
29+
db.runSql.bind(db, 'ALTER TABLE state_machines ADD CONSTRAINT fk_state_id FOREIGN KEY (current_state_id) REFERENCES states(id)'),
30+
db.runSql.bind(db, 'ALTER TABLE states ADD CONSTRAINT fk_state_machine_id FOREIGN KEY (state_machine_id) REFERENCES state_machines(id)'),
31+
db.runSql.bind(db, 'ALTER TABLE transitions ADD CONSTRAINT fk_state_id FOREIGN KEY (state_id) REFERENCES states(id)'),
32+
db.runSql.bind(db, 'ALTER TABLE transitions ADD CONSTRAINT fk_next_state_id FOREIGN KEY (next_state_id) REFERENCES states(id)')
33+
], callback)
34+
};
35+
36+
exports.down = function(db, callback) {
37+
async.series([
38+
db.dropTable.bind(db, 'transitions'),
39+
db.dropTable.bind(db, 'states'),
40+
db.dropTable.bind(db, 'state_machines')
41+
], callback)
42+
};

models/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('requireindex')(__dirname);

models/state.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
var sequelize = require('sequelize'),
2+
db = require('../lib/db.js')
3+
4+
var State = db.define('states', {
5+
name: sequelize.STRING,
6+
data: sequelize.TEXT
7+
})
8+
9+
module.exports = State

models/state_machine.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
var sequelize = require('sequelize'),
2+
db = require('../lib/db.js'),
3+
State = require('./state.js')
4+
5+
var StateMachine = db.define('state_machines', {
6+
})
7+
8+
StateMachine.hasOne(State, { foreignKey: 'current_state_id' })
9+
10+
StateMachine.hasMany(State)
11+
State.belongsTo(StateMachine)
12+
13+
module.exports = StateMachine

models/transition.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
var sequelize = require('sequelize'),
2+
db = require('../lib/db.js'),
3+
State = require('./state.js')
4+
5+
var Transition = db.define('transitions', {
6+
input: { type: sequelize.STRING, notNull: true }
7+
})
8+
9+
Transition.belongsTo(State, { as: 'State', foreignKey: 'state_id' })
10+
State.hasMany(Transition, { foreignKey: 'state_id'})
11+
12+
Transition.belongsTo(State, { as: 'NextState', foreignKey: 'next_state_id' })
13+
14+
module.exports = Transition

package.json

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "state-machine",
3+
"version": "0.0.1",
4+
"private": false,
5+
"dependencies": {
6+
"async": "0.2.7",
7+
"config": "0.4.22",
8+
"date-utils": "1.2.12",
9+
"db-migrate": "0.4.1",
10+
"pg": "1.0.0",
11+
"requireindex": "1.0.0",
12+
"restify": "2.3.5",
13+
"sequelize": "1.6.0",
14+
"sprintf": "0.1.1"
15+
},
16+
"engines": {
17+
"node": "0.10.x",
18+
"npm": "1.2.x"
19+
},
20+
"license": "MIT"
21+
}

restapi/resources/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('requireindex')(__dirname);

restapi/resources/v1/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('requireindex')(__dirname);

0 commit comments

Comments
 (0)