Compose your business logic into commands that validate input.
npm install cleanroom --save
import Cleanroom from 'cleanroom';
class UserSignUp extends Cleanroom.Command {
static schema = {
properties: {
email: { type: 'string', format: 'email' },
name: { type: 'string' },
newsletter_subscribe: { type: 'boolean' }
},
required: ['email', 'name'],
additionalProperties: false,
}
static execute(inputs) {
const user = new User(inputs);
// Do something with the user like save to a database.
// ...
return user;
}
}
Cleanroom.initCommand(UserSignUp);
// Sometime later in a file far, far away....
function signUpAction(inputs) {
const outcome = UserSignUp.run(inputs)
// Then check to see if it worked:
if (outcome.success) {
return { message: `Great success, ${outcome.result.name}!` };
} else {
return { errors: outcome.errors };
}
}
Some things to note about the example:
- Inputs are validated using a JSON Schema
- We can guarentee inputs pass the schema before reaching the business logic
- If
additionalProperties
is set to false, any additional properties will be removed. - This code is completely re-usable in other contexts
You have three choices. Given a command UserSignUp, you can do this:
const outcome = UserSignUp.run(inputs);
if (outcome.success) {
console.log(outcome.result);
} else {
console.error(outcome.errors);
}
Or, you can do this:
// returns the result of ::execute(), or throws ValidationError
try {
const result = UserSignUp.runExplicitly(inputs);
console.log(result);
} catch (e) {
console.error(e);
}
Or, you can do this:
// returns a Promise with the result of ::execute() as the resolved value,
// or rejects with the validation errors.
UserSignUp.runPromise(inputs)
.then(console.log)
.catch(console.error);
- Extend Cleanroom.Command:
class YourCommand extends Cleanroom.Command {
}
- Define your input schema:
Schemas are defined using the JSON Schema specification. See Understanding JSON Schema for basics on JSON Schema.
class YourCommand extends Cleanroom.Command {
static schema = {
properties: {
name: { type: 'string', maxLength: 10 },
state: { type: 'string', enum: ['AL', 'AK', 'AR', ...] },
age: { type: 'integer' },
isSpecial: { type: 'boolean', default: true },
account: { type: 'object' },
tags: { type: 'array', items: { type: 'string' } },
prefs: {
type: 'object',
properties: {
smoking: { type: 'boolean' },
view: { type: 'boolean' },
additionalProperties: false
}
}
},
required: ['name', 'state', 'age', 'isSpecial', 'account'],
additionalProperties: false
}
}
- Define your execute function. It can return a value:
class YourCommand extends Cleanroom.Command {
static schema = {
// ...
}
static execute(inputs) {
const record = doThing(inputs);
// ...
return record;
}
}
- Initialize your command:
Cleanroom.initCommand(YourCommand);
Validations are handled by the ajv library by epoberezkin.
Please see the ajv documenation until an overview of validation errors is written.
Highly inspired by cypriss/mutations from the Ruby world.