Skip to content

Browser sample

Aaron Hanusa edited this page Mar 11, 2019 · 30 revisions

This is the simplest possible example, which uses a reference to the peasy-js library and requires no transpilation or script loaders on your part. This complete sample can be viewed on plnkr.

If you would rather follow along using a package manager, feel free to install peasy-js with yarn or npm:

  • Install with yarn: yarn add peasy-js.
  • Install with npm: npm install peasy-js.

Start by creating the following html file (index.html) with the following snippet, which adds a script reference to peasy.js and the other files that we'll be creating:

<!DOCTYPE html>
<html>
  <head>
    <script src="peasy.js"></script>
    <script src="personDataProxy.js"></script>
    <script src="personNameRule.js"></script>
    <script src="validCityRule.js"></script>
    <script src="personService.js"></script>
    <script src="example.js"></script>
  </head>
  <body>
    <h3>Run your console debugger to see the output</h3>
  </body>
</html>

Next create a data proxy by creating a file (personDataProxy.js) with the following content:

// personDataProxy.js

class PersonDataProxy {

  constructor() {
    this._data = [];
  }

  getById(id) {
    var person = this._findBy(id);
    return Promise.resolve(person);
  }

  getAll() {
    return Promise.resolve(this._data);
  }

  insert(data) {
    data.id = this._data.length + 1;
    var newPerson = Object.assign({}, data);
    this._data.push(newPerson);
    return Promise.resolve(data);
  }

  update(data) {
    var person = this._findBy(data.id);
    Object.assign(person, data);
    return Promise.resolve(data);
  }

  destroy(id) {
    var person = this._findBy(id);
    var index = this._data.indexOf(person);
    this._data.splice(index, 1);
    return Promise.resolve();
  }

  _findBy(id) {
    var person = this._data.filter((function(p) {
      return p.id === id;
    }))[0];
    return person;
  }

}

Then, create a business service file (personService.js), which exposes CRUD commands responsible for subjecting data proxy invocations to business rules before execution:

// personService.js

class PersonService extends peasy.BusinessService {

  constructor(dataProxy) {
    super(dataProxy);
  }

}

Now let's consume our PersonService by creating a file (example.js) with the following contents:

// example.js

var personService = new PersonService(new PersonDataProxy());
var command = personService.insertCommand({name: "James Morrison"});

command.execute().then(result => {
  if (result.success) {
    console.log(result.value); // prints the inserted object with the assigned id
  }
});

Take a moment and run the application to view the output in your browser console (Note: you will see 404's returned for the files referenced in index.html that we have not yet created - they will disappear soon).

Let's create a business rule file (personNameRule.js) whose execution must be successful before the call to inject dataproxy.insert() function is invoked.

// personNameRule.js

class PersonNameRule extends peasy.Rule {

  constructor(name) {
    super();
    this.name = name;
    this.association = "name"; // optional
  }

  _onValidate() {
    if (this.name === "Jimi Hendrix") {
      this._invalidate("Name cannot be Jimi Hendrix");
    }
    return Promise.resolve();
  }
}

And wire it up in our PersonService to ensure that it gets fired before inserts:

// personService.js

class PersonService extends peasy.BusinessService {

  constructor(dataProxy) {
    super(dataProxy);
  }

  _getRulesForInsertCommand(person, context) {
    return Promise.resolve([
      new PersonNameRule(person.name)
    ]);
  }
}

And update our testing code

// example.js

var dataProxy = new PersonDataProxy();
var personService = new PersonService(dataProxy);
var command = personService.insertCommand({name: "Jimi Hendrix"});

command.execute().then(result => {
  if (result.success) {
    console.log(result.value);
  } else {
    console.log(result.errors[0]); // prints {association: "name", message: "Name cannot be Jimi Hendrix"}
  }
});

Take a moment and run the application to view the output in your browser console.

Let's create one more rule, just for fun:

// validCityRule.js

class ValidCityRule extends peasy.Rule {

  constructor(city) {
    super();
    this.city = city;
    this.association = "city"; // optional
  }

  _onValidate() {
    if (this.city === "Nowhere") {
      this._invalidate("Nowhere is not a city");
    }
    return Promise.resolve();
  }
}

We'll associate this one with inserts too:

// personService.js

class PersonService extends peasy.BusinessService {

  constructor(dataProxy) {
    super(dataProxy);
  }

  _getRulesForInsertCommand(person, context) {
    return Promise.resolve([
      new PersonNameRule(person.name),
      new ValidCityRule(person.city)
    ]);
  }
}

And update our testing code

// example.js

var dataProxy = new PersonDataProxy();
var personService = new PersonService(dataProxy);
var command = personService.insertCommand({name: "Jimi Hendrix", city: "Nowhere"});

command.execute().then(result => {
  if (result.success) {
    console.log(result.value);
  } else {
    console.log(result.errors[0]); // prints {association: "name", message: "Name cannot be Jimi Hendrix"}
    console.log(result.errors[1]); // prints {association: "city", message: "Nowhere is not a city"}
  }
});

Take a moment and run the application to view the output in your browser console.

Finally, let's pass in valid data and watch it be a success

// example.js
var dataProxy = new PersonDataProxy();
var personService = new PersonService(dataProxy);
var command = personService.insertCommand({name: "James Hendrix", city: "Madison"});

command.execute().then(result => {
  if (result.success) {
    console.log(result.value); // prints the inserted object with the assigned id
  } else {
    console.log(result.errors);
  }
});

Take a moment and run the application to view the output in your browser console.

Want to learn more?

  • View this sample in TypeScript.
  • View this sample in ES6 consuming it from node.js.
  • You can read in-depth coverage about peasy-js on the wiki.
  • An entire business logic implementation using peasy-js can be viewed here. This sample application is an order entry / inventory management system written with peasy-js, react, angular (with TypeScript), mongoDB, nodejs, and express.