Composite Models

This guide demonstrates the process of using Model compositeness functionality provided by oeCloud.io Framework.

Before we proceed, lets quickly understand what is Model Compositeness. In simple words, Model composition is the ability to treat multiple models together as a single entity. This should allow user to do certain operations on multiple models same as you do on single model. It also allows user to combine non related models and do similar operation you do with single model.

There are two types of model composition. Implicit Composite model and Explicit Composite model.

Implicit Composite : Loopback provides a way to relate one or more models using relations. oeCloud.io has extended this functionality. In oeCloud.io, you can use these relations while doing get/post operation to retrieve/save data from/to model. For example, you can get or post data to parent model and it’s children in single Web API call.

Explicit Composite : If models are not related and you wish to get or post data of those unrelated model using single operation, you require to construct explicit composite model where you have to configure that newly constructed composite model consists of what all other models.

What you will learn

In this example, you will

  • Implicit model composite
  • Explicit Model Composite
  • How to use Implicit model composite
  • How to use Explicit model composite

What you will need

To complete this guide, you will need the following -

  • an understanding of what a Model is in the context of the oeCloud.io. You can go through documentation on this portal.
  • an understanding of Relations of models in oeCloud.io
  • a running NodeJS application built using the oeCloud.io.

How to complete this Guide

Download and install scaffolding application from here.

Once you have started the server, open a browser link: http://localhost:3000/explorer/.

This Guide is divided into two section. One for implicit composite while other is for explicit composite.

Getting Started Implicit Composite

Create Customer and CustomerAddress Model

First we will create Customer Model. To create a model, you can follow the Model Creation guide to know how to create models.

  • The Customer model shoud have below schema. Note that customerAddressRel is being created with ‘hasMany’ type.
{
  "properties": {
  "name"  : {
                "type": "string"
                }
  },
  "readonly": false,
  "name": "customer",
  "description": "customer desc",
  "plural": "customers",
  "base": "BaseEntity",
  "validations": [
    {}
  ],
  "relations": {
  
    "addressRel": {
                  "type": "hasMany",
                  "model": "CustomerAddress",
                  "foreignKey": "customerId"  
                  }
  },
  "acls": [
    {}
  ],
  "methods": {},
  "id": 1
}
  • Now we will create CustomerAddress Model
{
  "properties": {
  "city"  : {
                "type": "string"
                }
  },
  "readonly": false,
  "name": "CustomerAddress",
  "description": "customer desc",
  "plural": "CustomerAddresses",
  "base": "BaseEntity",
  "strict": false,
  "public": true,
  "idInjection": false,
  "validateUpsert": false,
  "validations": [
    {}
  ],
  "relations": {},
  "acls": [
    {}
  ],
  "methods": {},
  "id": 2
}
  • Refresh swagger UI Page so that and see that both of these models appear.

POST data to Customer model and customerAddress model

Request API : POST http://localhost:3000/api/customers Request Data

{
                "name" : "john",
                "id": 1
}

CustomerAddress data - two records posted for customerId : 1

Request API : POST http://localhost:3000/api/CustomerAddresses Request data:

[
                {
                                "city" : "new york",
                                "id": 1,
                                "customerId" : 1
                },
                {
                                "city" : "chicago",
                                "id": 2,
                                "customerId" : 1
                }
]

Get data for customer as well as address using single API call

/api/Customers?filter={"include" : "addressRel" }
filter : {"include" : "addressRel" } 

above, you can execute by putting {“include” : “addressRel” } in filter section.

  • you should get data as shown below.
[
{
                "name" : "john",
                "id": 1,
                "addressRel" : [
                                {
                                                "city" : "new york",
                                                "id": 1,
                                                "customerId" : 1
                                },
                                {
                                                "city" : "chicago",
                                                "id": 2,
                                                "customerId" : 1
                                }
                ]
}
]

Implicit composite in action

  • Now you try creating new customer and his addresses all together in single post. Remember, we did posted twice before. one for customer and one for addresses.
[
  {
    "name": "dave",
    "id": 2,
    "addressRel": [
      {
        "city": "LA",
        "id": 21
      },
      {
        "city": "DC",
        "id": 22
      }
    ]
  }
]

Note in above that, with single post, we posted data of customer and addresses. Also tying addresses to customer was implicit.

Observations made Implicit Composite

  • You should get two records for customerId : 2 (dave) when you query customerAddress. Note customerId : 2 was added

/get/CustomerAddress filter : {“customerId” : 2 }

[
  {
    "city": "LA",
    "customerId": 2,
    "id": 21
  },
  {
    "city": "DC",
    "customerId": 2,
    "id": 22
  }
]
  • You should get two records for customer along with customerAddress to show that relation was really built
[
  {
    "name": "john",
    "id": 1,
    "addressRel": [
      {
        "city": "new york",
        "id": 1,
        "customerId": 1
      },
      {
        "city": "chicago",
        "id": 2,
        "customerId": 1
      }
    ]
  },
  {
    "name": "dave",
    "id": 2,
    "addressRel": [
      {
        "city": "LA",
        "customerId": 2,
        "id": 21
      },
      {
        "city": "DC",
        "customerId": 2,
        "id": 22
      }
    ]
  }
]

Getting Started Explicit Composite

Let’s consider home page screen we discussed in earlier section where you want to show data from various different models on single screen. This will let you make single web API call and fetch all data of related and unrelated models in one go.

As far as models are unrelated, you need to make composite model. Composite model definition is shown below. It consists of three models. Customer, Promotions and UpcomingEvents.

Homepage Models

Prepare Data

  • Create Customer, Account, AccountTransaction, Promotion and UpcomingEvents model by posting following data to modelDefinition model one by one.

Customer Model

{
  "properties": {
  "name"  : {
                "type": "string"
                }
  },
  "readonly": false,
  "name": "customer",
  "description": "customer desc",
  "plural": "customers",
  "base": "BaseEntity",
  "strict": false,
  "public": true,
  "idInjection": false,
  "validateUpsert": false,
  "validations": [
    {}
  ],
  "relations": {
    "accountRel": {
                  "type": "hasMany",
                  "model": "Account",
                  "foreignKey": "customerId"  
                  }
  },
  "acls": [
    {}
  ],
  "methods": {},
  "id": 1
}

Account Model

{
  "properties": {
  "accountType"  : {
                "type": "string"
                },
  "accountBalance"  : {
                "type": "number"
                }
  },
  "readonly": false,
  "name": "Account",
  "description": "Account desc",
  "plural": "Accounts",
  "base": "BaseEntity",
  "strict": false,
  "public": true,
  "idInjection": false,
  "validateUpsert": false,
  "validations": [
    {}
  ],
  "relations": {
  
    "transactionRel": {
                  "type": "hasMany",
                  "model": "AccountTransaction",
                  "foreignKey": "accountId"  
                  }
  },
  "acls": [
    {}
  ],
  "methods": {},
  "id": 2
}

Account Transaction Model

{
  "properties": {
  "transactionType"  : {
                "type": "string"
                },
  "amount"  : {
                "type": "number"
                }
  },
  "readonly": false,
  "name": "AccountTransaction",
  "description": "Account Transaction desc",
  "plural": "AccountTransactions",
  "base": "BaseEntity",
  "strict": false,
  "public": true,
  "idInjection": false,
  "validateUpsert": false,
  "validations": [
    {}
  ],
  "relations": {  },
  "acls": [
    {}
  ],
  "methods": {},
  "id": 3
}

Upcoming Events

{
  "properties": {
  "eventName"  : {
                "type": "string"
                },
  "active"  : {
                "type": "boolean"
                }
  },
  "readonly": false,
  "name": "UpcomingEvents",
  "description": "Upcoming Events desc",
  "plural": "UpcomingEvents",
  "base": "BaseEntity",
  "strict": false,
  "public": true,
  "idInjection": false,
  "validateUpsert": false,
  "validations": [
    {}
  ],
  "relations": {  },
  "acls": [
    {}
  ],
  "methods": {},
  "id": 4
}

Promotion

{
  "properties": {
    "name": { "type": "string" },
    "active": { "type": "boolean" }
  },
  "readonly": false,
  "name": "Promotion",
  "description": "Promotions desc",
  "plural": "Promotions",
  "base": "BaseEntity",
  "strict": false,
  "public": true,
  "idInjection": false,
  "validateUpsert": false,
  "validations": [ {} ],
  "relations": {},
  "acls": [ {} ],
  "methods": {},
  "id": 5
}
  • Creating Composite Model

Use following data to create model. Post following data to ModelDefinition model. Name of the model is HomePageModel. It consists of three models - Customer, UpcomingEvents and Promotion. End user should able to post / get data to all these models together by making single get/post call. Note that this composite model can be accessible with /api/HomePageModels URL.

{
"name": "HomePageModel",
  "mixins" : {
        "VersionMixin": false,
        "IdempotentMixin": false,
        "FailsafeObserverMixin": false
    }, 
"properties": {},
       "filebased": false,
       "CompositeTransaction" : true,
       "compositeModels": {
                "customer": {},
        "Promotion": {},
        "UpcomingEvents": {}
}
}
  • Posting data to Customer, account and Account Transaction model (this is where implicit composite will come into picture as all of these models are related ). Here you will see that we are creating two customers, accounts of those customer and transactions for those accounts in single post.
[
  {
    "name": "dave",
    "id": 1,
    "accountRel": [
      {
        "accountType": "savings",
        "id": 1,
        "accountBalance": 800,
        "transactionRel": [
          {
            "transactionType": "credit",
            "amount": 1000
          },
          {
            "transactionType": "debit",
            "amount": 100
          },
          {
            "transactionType": "debit",
            "amount": 100
          }
        ]
      },
      {
        "accountType": "loan",
        "id": 2,
        "accountBalance": 4700,
        "transactionRel": [
          {
            "transactionType": "credit",
            "amount": 5000
          },
          {
            "transactionType": "debit",
            "amount": 200
          }
        ]
      },
      {
        "accountType": "fd",
        "accountBalance": 10000,
        "id": 3
      }
    ]
  },
  {
    "name": "john",
    "id": 2,
    "accountRel": [
      {
        "accountType": "savings",
        "id": 21,
        "accountBalance": 9800,
        "transactionRel": [
          {
            "transactionType": "credit",
            "amount": 10000
          },
          {
            "transactionType": "debit",
            "amount": 100
          },
          {
            "transactionType": "debit",
            "amount": 100
          }
        ]
      },
      {
        "accountType": "fd",
        "accountBalance": 50000,
        "id": 23
      }
    ]
  }
]
  • Posting data to Upcoming Events and Promotions one after other

Upcoming Event Data

[
    {
        "eventName" : "Property Exhibition",
        "active" :true
    },
    {
        "eventName" : "Webinar on house buying",
        "active" :false
    }
]

Promotion Data

[
    {
        "name" : "Interest Discount Sale for xmas",
        "active" : true
    }
]

Explicit Composite in Action

  • Fetching data of Composite Model
/api/HomepageModels [get operation]

filter = { "Customer" : {"where": {"id" : 1 }, "include" : {"accountRel" : "accountTransactionRel"} }, "Promotions" : { "where" : { "active" : true }}, "UpcomingEvent" : {"where" :{"active" : true } } } 

Note the above format of filter. Filter has object with name of Model in composite. For example, above, it has three objects. Customer, Promotions and UpcomingEvents. Each object has got filter which is same as what is supported by loopback. Here, customer object has filter where clause which returns record for customer id : 1. Also include clause to include accountRel and accountTransactionRel. Promotion and UpcomingEvents has filter to ensure active:true records. This will return data of all the models defined in composite.

{
  "Customer": [
    {
      "name": "dave",
      "id": 1,
      "accountRel": [
        {
          "accountType": "savings",
          "id": 1,
          "customerId": 1,
          "accountBalance": 800,
          "transactionRel": [
            {
              "transactionType": "credit",
              "accountId": 1,
              "id": 1,
              "amount": 1000
            },
            {
              "transactionType": "debit",
              "accountId": 1,
              "id": 2,
              "amount": 100
            },
            {
              "transactionType": "debit",
              "accountId": 1,
              "amount": 100
            }
          ]
        },
        {
          "accountType": "loan",
          "id": 2,
          "customerId": 1,
          "accountBalance": 4700,
          "transactionRel": [
            {
              "transactionType": "credit",
              "accountId": 2,
              "id": 3,
              "amount": 5000
            },
            {
              "transactionType": "debit",
              "accountId": 2,
              "id": 4,
              "amount": 200
            }
          ]
        },
        {
          "accountType": "fd",
          "accountBalance": 10000,
          "customerId": 1,
          "id": 3
        }
      ]
    }
  ],
  "UpcomingEvents": [
    {
      "eventName": "Property Exhibition",
      "active": true,
      "id": 1
    }
  ],
  "Promotions": [
    {
      "name": "Interest Discount Sale for xmas",
      "active": true,
      "id": 1
    }
  ]
}
  • Posting data to Composite Model

This is very tricky part of explicit composite. With this, you should able to add, update or remove records of models using single post. The key is, you must tell what to do with the record by having __row_status field for each record. Consider following post data.

/api/HomepageModels [POST operation]
{
  "customer": [
    {
      "name": "dave changed",
      "id": 1,
      "__row_status": "modified",
      "accountRel": [
        {
          "accountType": "savings",
          "id": 1,
          "customerId": 1,
          "accountBalance": 1800,
          "__row_status": "modified",
          "transactionRel": [
            {
              "transactionType": "credit",
              "id": 1,
              "amount": 1000,
              "__row_status": "added"
            }
          ]
        },
        {
          "accountType": "loan",
          "id": 2,
          "customerId": 1,
          "accountBalance": 4600,
          "__row_status": "modified",
          "transactionRel": [
            {
              "transactionType": "debit",
              "accountId": 2,
              "id": 3,
              "amount": 100,
              "__row_status": "added"
            }
          ]
        }
      ]
    }
  ],
  "UpcomingEvents": [
    {
      "eventName": "Property Exhibition",
      "active": true,
      "__row_status": "deleted",
      "id": 1
    }
  ],
  "Promotion": [
    {
      "name": "Interest Discount Sale for xmas",
      "active": false,
      "__row_status": "modified",
      "id": 1
    }
  ]
}

You should see data is changed when you query composite model using swagger.

/api/HomepageModels [get operation]

filter = { "Customer" : {"where": {"id" : 1 }, "include" : {"accountRel" : "accountTransactionRel"} }, "Promotions" : { "where" : { "active" : true }}, "UpcomingEvent" : {"where" :{"active" : true } } } 

If you see above, we removed upcomingEvent record as __row_status was set to deleted. Promotion record was updated with active status set to false. Customer record was updated with name was changed. Account records of customer was updated with new balance. AccountTransaction model has new records created as row_status was added.

This entire operation would run in single transaction and any of that is failed, transaction would be rolled back.

Observations

  • You should see in mongo database directly and see Customer record is changed
  • You should see Account collection of mongo db to see AccountBalance is updated
  • You should see in AccountTransaction collection that new entries are created.

You should not get anything in Promotion and UpcomingEvent model.

Summary

  • Implicit composite is simple way to post data (put/post operations) to model in similar way you get the data with include clause. It his implicit because it is based on relations.
  • Explicit composite is way to treat one or more unrelated models as single entity and get/post data. This is useful to get data for page from multiple models.
  • Explicit composite require row_status field which indicate status of each record.