Blog

How to Perform Complete Business Logic in One API Call

by on May 30, 2019

Perform Business Logic in One API Call

In a previous article (How to Save an Object with All the Children in a Single Call to Server), we examined how to simply save an object model. However, Backendless custom services give us much more flexibility when it comes to saving objects.

In this article, we are going to cover how to perform complex business logic (Cloud Code) actions such as saving an object with calculated information in one API call using custom services. As we’ll demonstrate in this example, you can actually encapsulate entire portions of your business logic on the server-side.

For this example, we will build a custom service that will emulate the order process for an automotive technician service station.

We are going to extend the model from the previous article and use it in the API service that will create an order (as a new record in the database) and attach to it already existing data (dynamic and constant). The same service also will calculate some values on the fly.

Unlike the example where we simply save objects and relations, this one needs some work on the client-side as well. We will cover this in more detail later in the article.

Table configuration

We are going to take the model from the previous article and extend it with the following changes (as usual, if you are anxious to test, use the db export). The tables we will be using are described below:

Add a Data Table in Backendless

person

… other columns …
discountDOUBLE

Person Table

auto

body_typeSTRING
brandSTRING
colorSTRING
engine_capacityINT
modelSTRING
yearINT

Auto Table

work_type

nameSTRING
priceDOUBLE

Fill the table with the values:

  • replacement of brake shoes – 53
  • oil change – 24
  • light calibration – 81

Work Type Table

material_type

nameSTRING
priceDOUBLE

Fill the table with the values:

  • brake shoes – 153.48
  • oil – 8.33

Material Type Table

service_order

customerData Relation 1:1 person
vehicleData Relation 1:1 auto
materialsData Relation 1:N material_type
serviceData Relation 1:N work_type
discountDOUBLE
total_costDOUBLE

Service Order Table Schema

Custom service

Now we will create a service that will perform all of our business logic work. (To save you time, we have prepared source files for you to use in CodeRunner or your editor of choice: src_SaveObjectModel_car_service.)

The steps are similar to the previous example, but the result differs.

Step 1. Download CodeRunner and model classes.

First, we need to download CodeRunner. If you aren’t familiar with it and server business logic, please visit our documentation here.

Download CodeRunner for Java

Second, we need to model Java classes for our table model. Use the Code Generation section to get them.

Generate Java Classes Code

Step 2. Project creation.

After downloading, you need to unpack the zip-archive that contains CodeRunner and open it as a project in IntelliJ IDEA.

From the second archive, you’ll need the data package with the model classes for your tables. Just put the package data in the com.<app_name> package in the opened project.

Then create class SaveObjectModel in the package services.

The structure should be like this:

Java Object Structure

Step 3. Bootstrap class.

Please note, we renamed classes for convenience and created special data mapping in the Bootstrap class.

public class Bootstrap implements IBackendlessBootstrap
{
 @Override
 public void onStart()
 {
   Backendless.Persistence.mapTableToClass( "auto", Auto.class );
   Backendless.Persistence.mapTableToClass( "person", Person.class );
   Backendless.Persistence.mapTableToClass( "material", Material.class );
   Backendless.Persistence.mapTableToClass( "type_of_work", TypeOfWork.class );
   Backendless.Persistence.mapTableToClass( "service_order", ServiceOrder.class );

   Backendless.Persistence.mapTableToClass( "auto", Auto.class );
   Backendless.Persistence.mapTableToClass( "material_type", MaterialType.class );
   Backendless.Persistence.mapTableToClass( "work_type", WorkType.class );
   Backendless.Persistence.mapTableToClass( "service_order", ServiceOrder.class );

   // add your code here
 }
  
 @Override
 public void onStop()
 {
   // add your code here
 }
}

Step 4. The service.

Please read through the comments in the following code.

SaveObjectModel

import com.backendless.Backendless;
import com.backendless.servercode.BackendlessService;
import com.testapp3.data.Auto;
import com.testapp3.data.Material;
import com.testapp3.data.Person;
import com.testapp3.data.ServiceOrder;
import com.testapp3.data.TypeOfWork;

import java.util.Collections;
import java.util.List;


@BackendlessService
public class SaveObjectModel
{
public String createServiceOrder(ServiceOrder order)
{
 // save all related data in separate variables
 Person customer = order.getCustomer();
 Auto vehicle = order.getVehicle();
 List<MaterialType> materials = order.getMaterials();
 List<WorkType> services = order.getService();


 // Let's count here the total cost of the services and materials
 double cost = 0;
 for( MaterialType mt : materials )
   cost += Backendless.Data.of( MaterialType.class ).findById( mt.getObjectId() ).getPrice();

 for( WorkType wt : services )
   cost += Backendless.Data.of( WorkType.class ).findById( wt.getObjectId() ).getPrice();

 // Here we apply the customer discount and the order discount
 cost += cost * (1 - (customer.getDiscount() + order.getDiscount()));
 order.setTotal_cost( cost );


 // save main object
 ServiceOrder savedOrder = Backendless.Data.save( order );

 // save vehicle
 Auto savedVehicle = Backendless.Data.save( vehicle );

 // create relations
 Backendless.Data.of( ServiceOrder.class )
         .setRelation( savedOrder, "customer", Collections.singletonList( customer ) );
 Backendless.Data.of( ServiceOrder.class )
         .setRelation( savedOrder, "vehicle", Collections.singletonList( savedVehicle ) );
 Backendless.Data.of( ServiceOrder.class )
         .setRelation( savedOrder, "materials", materials );
 Backendless.Data.of( ServiceOrder.class )
         .setRelation( savedOrder, "service", services );

 return savedOrder.getObjectId();
}
}

Step 5. Deploy service

– Build your project

– Use run file …<your_project>/bin/Deploy.sh to deploy the service to Backendless server.

Step 6. Client side

As you might have noticed from the database scheme, we have two tables with constant data. It is material_type and work_type. This is a way to reduce the number of requests to the database both on client and server-side.

We can get all necessary constant data during loading of the application and then use them further in the code.

public static enum Material
{
 oil("oil"), brake_shoes("brake shoes");

 private String nameInDB;
 private String objectId;

 Material( String nameInDB )
 {
   this.nameInDB = nameInDB;
 }

 public String getObjectId()
 {
   return objectId;
 }

 public void setObjectId( String objectId )
 {
   this.objectId = objectId;
 }
}

public static enum Work
{
 brake_shoes_replacement("replacement of brake shoes"), oil_change("oil change"), light_calibration("light calibration");

 private String nameInDB;
 private String objectId;

 Work( String nameInDB )
 {
   this.nameInDB = nameInDB;
 }

 public String getObjectId()
 {
   return objectId;
 }

 public void setObjectId( String objectId )
 {
   this.objectId = objectId;
 }
}

// fill in our constants with actual information from database
public static void getConstantData()
{
 Stream.of( Material.values() ).forEach( material -> {
   DataQueryBuilder dataQueryBuilder = DataQueryBuilder.create().setWhereClause( "name='" + material.nameInDB + "'" );
   MaterialType materialType = Backendless.Data.of( MaterialType.class ).find( dataQueryBuilder ).get( 0 );
   material.setObjectId( materialType.getObjectId() );
 } );

 Stream.of( Work.values() ).forEach( work -> {
   DataQueryBuilder dataQueryBuilder = DataQueryBuilder.create().setWhereClause( "name='" + work.nameInDB + "'" );
   WorkType workType = Backendless.Data.of( WorkType.class ).find( dataQueryBuilder ).get( 0 );
   work.setObjectId( workType.getObjectId() );
 } );
}

Business logic service test

Test the code in a plain Java app. You should copy the onStart() method with table mappings from the custom service to your test class.

import com.backendless.Backendless;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

public class CreateRecordTest
{
 public static final String APPLICATION_ID = "";
 public static final String API_KEY = "";

 public static void main( String[] args )
 {
   testServiceOrder();
 }

 public static void testServiceOrder()
 {
   Backendless.initApp( APPLICATION_ID, API_KEY );

   // these methods should be invoked at the beginning
   onStart();
   getConstantData(); // method described in the previous step

   // set the customer personal discount
   Person person = Backendless.Data.of( Person.class ).findById( "19386945-F71B-162A-FFB4-0EE33D4FF300" );
   person.setDiscount( 0.05 );
   Backendless.Data.of( Person.class ).save( person );


   // we create almost empty objects only with objectId to reduce the amount of data transferred through the network
   // i’ve also added constructors for MaterialType and WorkType
   List<MaterialType> materialTypes = new ArrayList<>();
   materialTypes.add( new MaterialType( Material.brake_shoes.getObjectId() ) );
   materialTypes.add( new MaterialType( Material.oil.getObjectId() ) );

   List<WorkType> workTypes = new ArrayList<>();
   workTypes.add( new WorkType( Work.brake_shoes_replacement.getObjectId() ) );
   workTypes.add( new WorkType( Work.oil_change.getObjectId() ) );
   workTypes.add( new WorkType( Work.light_calibration.getObjectId() ) );

   Auto auto = new Auto();
   auto.setBrand( "Mazda" );
   auto.setBody_type( "hatchback" );
   auto.setModel( "m3" );
   auto.setColor( "Red" );
   auto.setYear( 2013 );
   auto.setEngine_capacity( 1600 );

   ServiceOrder serviceOrder = new ServiceOrder();
   serviceOrder.setCustomer( new Person( "19386945-F71B-162A-FFB4-0EE33D4FF300" ) );
   serviceOrder.setVehicle( auto );
   serviceOrder.setMaterials( materialTypes );
   serviceOrder.setService( workTypes );
   serviceOrder.setDiscount( 0.03 );

   // the final action
   String serviceOrderId = Backendless.CustomService.invoke( "SaveObjectModel", "createServiceOrder", new Object[] {serviceOrder} );
   ServiceOrder savedServiceOrder = Backendless.Data.of( ServiceOrder.class ).findById( serviceOrderId );
   System.out.println("Service order: " + savedServiceOrder.getObjectId());
 }
}

That’s all for today. You can find our archive with test classes here. Thanks for reading, and happy coding!

Leave a Reply