Service Development¶
Service implementation will consist of four classes:
- The API service implementation is in the
ShoppingCartService
class. - There are two data model classes:
ShoppingItem
andOrder,
they are used to represent data objects stored in Backendless Data Service - The
ShoppingCart
class is used by the service implementation to store any intermediate state between the service calls. - Create the
/app/models
directory in the root directory of your project. The directory will contain classes defining your data model. - Create
shopping-item.js
in the/app/models/
directory with the following contents:
'use strict';
/**
* @property {String} objectId
* @property {String} product
* @property {Number} price
* @property {Number} quantity
*/
class ShoppingItem extends Backendless.ServerCode.PersistenceItem {
}
module.exports = Backendless.ServerCode.addType(ShoppingItem);
Notice the following details about the code above: The class extends
Backendless.ServerCode.PersistentItem
. Since instances of the ShoppingItem class are stored in the Backendless data storage, this inheritance adds for convenience several instance methods like save
, remove
, loadRelations
, .., and static class methods like find
, findLast
, findById
, ...
* The Backendless.ServerCode.addType
method registers the class as one of the types used during server code invocation. That means that for any service method which uses the class as an argument, CodeRunnerâ„¢ will be able to adapt/cast/convert incoming argument objects into instance of the ShoppingItem
class. For added convenience, the addType
method returns the passed object.
* The ShoppingItem
class is added to module.exports
. This is done to enable access to this class from other modules.
* The annotation in the comment for the method is very* important. It is processed by Backendless and is used for service API generation.
1. Create order.js
in the /app/models/
directory with the following contents:
'use strict';
class Order extends Backendless.ServerCode.PersistenceItem {
constructor(items) {
super();
/**
* @type {ShoppingItem[]}
*/
this.items = items || [];
/**
* @type {Number}
*/
this.orderPrice = this.items.reduce((sum, item) => {
return (sum || 0) + (item.price * item.quantity);
}, 0);
}
}
module.exports = Backendless.ServerCode.addType(Order);
shopping-cart.js
in the /app/models/
directory with the following contents:
'use strict';
class ShoppingCart {
constructor(opts) {
opts = opts || {};
this.name = opts.name;
this.items = opts.items || [];
this.___class = ShoppingCart.name;
}
addItem(item) {
item.objectId = null;
this.items.push(item);
}
deleteItem(product) {
const idx = this.items.findIndex(item => item.product === product);
if (idx === -1) {
throw new Error(`No ${product} in cart`);
}
this.items.splice(idx, 1);
return this;
}
setQuantity(product, quantity) {
const productItem = this.items.find(item => item.product === product);
if (!productItem) {
throw new Error(`No [${product}] in cart`);
}
productItem.quantity = quantity;
return this;
}
getItems() {
return this.items;
}
destroy() {
return Backendless.Cache.remove(this.name, this);
}
save() {
return Backendless.Cache.put(this.name, this);
}
static get(name, mustExist) {
Backendless.Cache.setObjectFactory(ShoppingCart.name, ShoppingCart);
return Backendless.Cache.get(name).then(cart => {
if (!cart && mustExist) {
throw new Error(`Shopping cart [${name}] does not exist`);
}
return cart;
});
}
}
module.exports = ShoppingCart;
/app/services
directory in the project's root directory.
1. Create shopping-cart-service.js
in the /app/services/
directory with the following contents:
'use strict';
const Order = require('../models/order'),
ShoppingCart = require('../models/shopping-cart');
class ShoppingCartService {
/**
* @param {String} cartName
* @param {ShoppingItem} item
* @returns {Promise.<void>}
*/
addItem(cartName, item) {
return this.addItems(cartName, [item]);
}
/**
* @param {String} cartName
* @param {Array.<ShoppingItem>} items
* @returns {Promise.<void>}
*/
addItems(cartName, items) {
return ShoppingCart.get(cartName).then(cart => {
if (!cart) {
cart = new ShoppingCart({ name: cartName });
}
items.forEach(item => cart.addItem(item));
return cart.save();
});
}
/**
* @param {String} cartName
* @param {String} product
* @returns {Promise.<void>}
*/
deleteItem(cartName, product) {
return ShoppingCart.get(cartName, true).then(cart => cart.deleteItem(product).save());
}
/**
* @param {String} cartName
* @returns {Promise.<Array.<ShoppingItem>>}
*/
getItems(cartName) {
return ShoppingCart.get(cartName, true).then(cart => cart.getItems());
}
/**
* @param {String} cartName
* @param {String} productName
* @param {Number} quantity
* @returns {Promise.<void>}
*/
setQuantity(cartName, productName, quantity) {
return ShoppingCart.get(cartName, true).then(cart => cart.setQuantity(productName, quantity).save());
}
/**
* @param {String} cartName
* @returns {Promise.<Order>}
*/
purchase(cartName) {
return ShoppingCart.get(cartName, true).then(cart => {
const order = new Order(cart.getItems());
return order.save()
.then(() => cart.destroy())
.then(() => order);
});
}
}
Backendless.ServerCode.addService(ShoppingCartService);
Notice the following details about the code above:
- The service class is registered with the
Backendless.ServerCode.addService
method. - All methods of a class registered as service become individual API endpoints when the service is deployed into CodeRunnerâ„¢.
- The annotations in the comments are used by Backendless to create service API declaration.
When you are done with this part of the guide, your project's directory structure should look as in the screenshot below: