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.
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:
person
… other columns … | |
discount | DOUBLE |
auto
body_type | STRING |
brand | STRING |
color | STRING |
engine_capacity | INT |
model | STRING |
year | INT |
work_type
name | STRING |
price | DOUBLE |
Fill the table with the values:
material_type
name | STRING |
price | DOUBLE |
Fill the table with the values:
service_order
customer | Data Relation 1:1 person |
vehicle | Data Relation 1:1 auto |
materials | Data Relation 1:N material_type |
service | Data Relation 1:N work_type |
discount | DOUBLE |
total_cost | DOUBLE |
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.
First, we need to download CodeRunner. If you aren’t familiar with it and server business logic, please visit our documentation here.
Second, we need to model Java classes for our table model. Use the Code Generation section to get them.
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:
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 } }
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(); } }
– Build your project
– Use run file …<your_project>/bin/Deploy.sh to deploy the service to Backendless server.
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() ); } ); }
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!