Blockchain App Builder takes the input from your specification file and generates a fully-functional scaffolded chaincode project.
If the chaincode project uses the TypeScript language, the scaffolded project contains three main files:
main.ts
<chaincodeName>.model.ts
<chaincodeName>.controller.ts
All the necessary libraries are installed and packaged. The
tsconfig.json
file contains the necessary configuration to compile and build the TypeScript project.
The <chaincodeName>.model.ts
contains multiple asset definitions and <chaincodeName>.controller.ts
contains the assets behavior and CRUD methods.
The various decorators in model.ts
and controller.ts
provide support for features like automatic validation of arguments, marshalling/unmarshalling of arguments, transparent persistence capability (ORM) and calling rich queries.
Asset
By default every class which extends
OchainModel
will have an additional read-only property called
assetType
. This property can be used to fetch only assets of this type. Any changes to this property are ignored during the creation and updating of the asset. The property value by default is
<chaincodeName>.<assetName>
.
@Id('supplierId')
export class Supplier extends OchainModel<Supplier> {
public readonly assetType = 'tsdeml36.supplier';
@Mandatory()
@Validate(yup.string())
public supplierId: string;
Decorators
-
Class decorators
@Id(identifier)
- This decorator identifies the property which uniquely defines the underlying asset. This property is used as a key of the record, which represents this asset in the chaincode's state. This decorator is automatically applied when a new TypeScript project is scaffolded. The 'identifier' argument of the decorator takes the value from specification file.
@Id('supplierId')
export class Supplier extends OchainModel{
...
}
-
Property decorators
- Multiple property decorators can be used. The decorators are resolved in top to bottom order.
@Mandatory()
- This marks the following property as mandatory so it cannot be skipped while saving to the ledger. If skipped it throws an error.
@Mandatory()
public supplierID: string;
@Default(param)
- This property can have a default value. The default value in the argument (
param
) is used when the property is skipped while saving to the ledger.@Default('open for business')
@Validate(yup.string())
public remarks: string;
@Validate(param)
- The following property is validated against the schema presented in the parameter. The argument
param
takes a yup schema and many schema methods can be chained together. Many complex validations can be added. Refer to https://www.npmjs.com/package/yup for more details.@Validate(yup.number().min(3))
public productsShipped: number;
@Embedded(PropertyClass)
- This property decorator marks the underlying property as an embeddable asset. It takes the embeddable class as a parameter. This class should extend the
EmbeddedModel
class. This is validated by the decorator.
- In this example,
Employee
has a property called address
of type Address
, which is to be embedded with the Employee
asset. This is denoted by the @Embedded()
decorator.
export class Employee extends OchainModel<Employee> {
public readonly assetType = 'TsSample.employee';
@Mandatory()
@Validate(yup.string())
public emplyeeID: string;
@Mandatory()
@Validate(yup.string().max(30))
public firstName: string;
@Mandatory()
@Validate(yup.string().max(30))
public lastName: string;
@Validate(yup.number().positive().min(18))
public age: number;
@Embedded(Address)
public address: Address;
}
export class Address extends EmbeddedModel<Address> {
@Validate(yup.string())
public street: string;
@Validate(yup.string())
public city: string;
@Validate(yup.string())
public state: string;
@Validate(yup.string())
public country: string;
}
- When a new instance of the
Address
class is created, all the properties of the Address
class are automatically validated by the @Validate()
decorator. Note that the Address
class does not have the assetType
property or @Id()
class decorator. This asset and its properties are not saved in the ledger separately but are saved along with the Employee
asset. Embedded assets are user defined classes that function as value types. The instance of this class can only be stored in the ledger as a part of the containing object (OchainModel
assets). All the above decorators are applied automatically based on the input file while scaffolding the project.
@Derived(STRATEGY, ALGORITHM, FORMAT)
- This decorator is used for defining the attribute derived from other properties. This decorator has two mandatory parameters:
STRATEGY
: takes values of CONCAT
or HASH
. Requires an additional parameter ALGORITHM
if HASH
is selected. The default algorithm is sha256
; md5
is also supported.
FORMAT
: takes an array of specification strings and values to be used by the strategy.
@Id('supplierID')
export class Supplier extends OchainModel<Supplier> {
public readonly assetType = 'chaincodeTS.supplier';
@Mandatory()
@Derived(STRATEGY.HASH.'sha256',['IND%1IND%2','license','name'])
@Validate(yup.string())
public supplierID: string;
@Validate(yup.string().min(2).max(4))
public license: string;
@Validate(yup.string().min(2).max(4))
public name: string;
-
Method decorators
@Validator(…params)
- This decorator is applied on methods of the main controller class. This decorator is important for parsing the arguments, validating against all the property decorators and returning a model/type object. It takes multiple user created models or yup schemas as parameter.
- Note the order of the parameters should be exactly the same as the order of the arguments in the method.
- In this example, the
Supplier
model reference is passed in parameters which corresponds to the asset
type in the method argument. The decorator in run-time would parse and convert the method argument to JSON object, validate against the Supplier
validators, and upon successful validation convert the JSON object to Supplier
object and assign it to the asset
variable. On completion the underlying method is then finally called.@Validator(Supplier)
public async createSupplier(asset: Supplier) {
return await asset.save();
}
- In this example, multiple asset references are passed; they corresponds to the object types of the method arguments. Notice the order in the parameters.
@Validator(Supplier, Manufacturer)
public async createProducts(supplier: Supplier, manufacturer: Manufacturer) {
}
- Apart from asset reference, yup schema objects could also be passed if the arguments are of basic-types. In this example,
supplierId
and rawMaterialSupply
are of type string
and number
respectively, so the yup schema of similar type and correct order is passed to the decorator. Notice the chaining of yup schema methods.@Validator(yup.string(), yup.number().positive())
public async fetchRawMaterial(supplierID:string, rawMaterialSupply: number) {
const supplier = await Supplier.get(supplierID);
supplier.rawMaterialAvailable = supplier.rawMaterialAvailable + rawMaterialSupply;
return await supplier.update();
}
Models
Every model class extends OchainModel
. Transparent Persistence Capability or simplified ORM is captured in the OchainModel
class. If your model needs to need call any of the below ORM methods, you should extend the OchainModel
class.
ORM methods which are exposed via
OchainModel
:
save
– this calls the Hyperledger Fabric putState
method
get
– this calls the Hyperledger Fabric getState
method
update
– this calls the Hyperledger Fabric putState
method
delete
– this calls the Hyperledger Fabric deleteState
method
history
– this calls the Hyperledger Fabric getHistoryForKey
method
getByRange
– this calls the Hyperledger Fabric getStateByRange
method
See:
Model Methods.
Controller
Main controller class extends OchainController
. There is only one main controller.
export class TSProjectController extends OchainController{
You can create any number of classes, functions, or files, but only those methods that are defined within the main controller class are invokable from outside, the rest of them are hidden.
CRUD Methods
As described in Input Specification File, you can specify which CRUD methods you want generated in the specification file. For example, if you selected to generate all methods, the result would be similar to:
@Validator(Supplier)
public asynch createSupplier(asset: Supplier){
return await asset.save();
}
public asynch getSupplierById(id: string){
const asset = await Supplier.get(id);
return asset;
}
@Validator(Supplier)
public asynch updateSupplier(asset: Supplier){
return await asset.update();
}
public asynch deleteSupplier(id: string){
const result = await Supplier.delete(id);
return result;
}
public asynch getSupplierHistoryById(id: string){
const result = await Supplier.history(id);
return result;
}
@Validator(yup.string(), yup.string())
public asynch getSupplierByRange(startId: string, endId: string){
const result = await Supplier.getByRange(startId, endId);
return result;
}
Model Methods
-
save
- The
save
method adds the caller asset
details to the ledger.
- This method calls the Hyperledger Fabric
putState
internally. All marshalling/unmarshalling is handled internally.
<Asset>.save(extraMetadata?: any): Promise<any>
- Parameters:
extraMetadata : any
(optional) – To save metadata apart from the asset into the ledger.
- Returns:
Promise<any>
- Returns a promise on completion
- Example:
@Validator(Supplier)
public async createSupplier(asset: Supplier) {
return await asset.save();
}
-
get
- The
get
method is a static method of OchainModel
class which is inherited by the concrete model classes of {chaincodeName}.model.ts
.
- This returns an asset of
<Asset>
if id
is found in the ledger and has the same type as <Asset>
. This method calls the Hyperledger Fabric getState
method internally. Even though any asset with givenid
is returned from the ledger, our method will take care of casting into the caller Model
type.
- If you would like to return any asset by the given
id
, use the generic controller method getAssetById
.
<Asset>.get(id: string): Promise<asset>
- Parameters:
id : string
– Key used to save data into the ledger.
- Returns:
Promise: <Asset>
- Returns object of type <Asset>
. Even though any asset with given id
is returned from the ledger, this method will take care of casting into the caller Asset
type. If the asset returned from the ledger is not of the Asset
type, then it throws an error. This check is done by the read-only assetType
property in the Model
class.
- Example:
public async getSupplierById: string) {
const asset = await Supplier.get(id);
return asset;
}
In the example, asset
is of the type Supplier
.
-
update
- The
update
method updates the caller asset
details in the ledger. This method returns a promise.
- This method calls the Hyperledger Fabric
putState
internally. All the marshalling/unmarshalling is handled internally.
<Asset>.update(extraMetadata?: any): Promise<any>
- Parameters:
extraMetadata : any
(optional) – To save metadata apart from the asset into the ledger.
- Returns:
Promise<any>
- Returns a promise on completion
- Example:
@Validator(Supplier)
public async updateSupplier(asset: Supplier) {
return await asset.update();
}
-
delete
- This deletes the asset from the ledger given by
id
if it exists. This method calls the Hyperledger Fabric deleteState
method internally.
- The
delete
method is a static method of OchainModel
class which is inherited by the concrete Model
classes of {chaincodeName}.model.ts
.
<Asset>. delete(id: string): Promise<any>
- Parameters:
id : string
– Key used to save data into the ledger.
- Returns:
Promise <any>
- Returns a promise on completion.
- Example:
public async deleteSupplier(id: string) {
const result = await Supplier.delete(id);
return result;
}
-
history
- The history method is a static method of
OchainModel
class which is inherited by the concrete Model
classes of {chaincodeName}.model.ts
. This returns the asset history given by id
from the ledger, if it exists.
- This method calls the Hyperledger Fabric
getHistoryForKey
method internally.
<Asset>.history(id: string): Promise<any[]>
- Parameters:
id : string
– Key used to save data into the ledger.
- Returns:
Promise <any[]>
- Returns any [] on completion.
- Example
public async getSupplierHistoryById(id: string) {
const result = await Supplier.history(id);
return result;
}
- Example of the returned asset history for
getSupplierHistoryById
:[
{
"trxId": "8ef4eae6389e9d592a475c47d7d9fe6253618ca3ae0bcf77b5de57be6d6c3829",
"timeStamp": 1602568005,
"isDelete": false,
"value": {
"assetType": "supp.supplier",
"supplierId": "s01",
"rawMaterialAvailable": 10,
"license": "abcdabcdabcd",
"expiryDate": "2020-05-28T18:30:00.000Z",
"active": true
}
},
{
"trxId": "92c772ce41ab75aec2c05d17d7ca9238ce85c33795308296eabfd41ad34e1499",
"timeStamp": 1602568147,
"isDelete": false,
"value": {
"assetType": "supp.supplier",
"supplierId": "s01",
"rawMaterialAvailable": 15,
"license": "valid license",
"expiryDate": "2020-05-28T18:30:00.000Z",
"active": true
}
}
]
-
getByRange
- The
getByRange
method is a static method of OchainModel
class which is inherited by the concrete Model
classes of {chaincodeName}.model.ts
.
- This returns a list of asset between the range
startId
and endId
. This method calls the Hyperledger Fabric getStateByRange
method internally.
- Even though any asset with given
id
is returned from the ledger, our method will take care of casting into the caller Model
type. In above example, result
array is of the type Supplier
. If the asset returned from ledger is not of the Model
type, then it will not be included in the list. This check is done by the read-only assetType
property in the Model
class.
- If you would like to return all the assets between the range
startId
and endId
, use the generic controller method getAssetsByRange
.
<Asset>.getByRange(startId: string, endId: string): Promise<Asset[]>
- Parameters:
startId : string
– Starting key of the range. Included in the range.
endId : string
– Ending key of the range. Excluded of the range.
- Returns:
Promise< Asset[ ] >
- Returns array of <Asset>
on completion.
- Example:
@Validator(yup.string(), yup.string())
public async getSupplierByRange(startId: string, endId: string){
const result = await Supplier.getByRange(startId, endId);
return result;
}
-
getId
- When the asset has a derived key as
Id
, you can use this method to get a derived ID. This method will return an error if the derived key contains %t
(timestamp).
- Parameters:
object
– Object should contain all the properties on which the derived key is dependent.
- Returns:
- Returns the derived key as a string.
- Example:
@Validator(yup.string(), yup.string())
public async customGetterForSupplier(license: string, name: string){
let object = {
license : license,
name: name
}
const id = await Supplier.getID(object);
return Supplier.get(id);
}
Controller Method Details
Apart from the above model CRUD and non-CRUD methods, Blockchain App Builder provides out-of-the box support for other Hyperledger Fabric methods from our controller. These methods are:
getAssetById
getAssetsByRange
getAssetHistoryById
query
generateCompositeKey
getByCompositeKey
getTransactionId
getTransactionTimestamp
getTransactionInvoker
getChannelID
getCreator
getSignedProposal
getArgs
getStringArgs
getMspID
getNetworkStub
These methods are available with
this
context itself in the
Controller
class. For example:
public async getModelById(id: string) {
const asset = await this.getAssetById(id);
return asset;
}
@Validator(yup.string(), yup.string())
public async getModelsByRange(startId: string, endId: string) {
const asset = await this.getAssetsByRange(startId, endId);
return asset;
}
public async getModelHistoryById(id: string) {
const result = await this.getAssetHistoryById(id);
return result;
}
-
getAssetById
- The
getAssetById
method returns asset based on id
provided. This is a generic method and be used to get asset of any type.
this< OchainController>.getAssetById(id: string): Promise<byte[]>
- Parameters:
id : string
– Key used to save data into the ledger.
- Returns:
Promise <byte [ ]>
- Returns promise on completion. You have to convert byte[]
into an object.
-
getAssetsByRange
- The
getAssetsByRange
method returns all assets present from startId
(inclusive) to endId
(exclusive) irrespective of asset types. This is a generic method and can be used to get assets of any type.
this<OchainController>.getAssetsByRange(startId: string, endId: string):
Promise<shim.Iterators.StateQueryIterator>
- Parameters:
startId : string
– Starting key of the range. Included in the range.
endId : string
– Ending key of the range. Excluded of the range.
- Returns:
Promise< shim.Iterators.StateQueryIterator>
- Returns an iterator on completion. You have to iterate over it.
-
getAssetHistoryById
- The
getAssetHistoryById
method returns history iterator of an asset for id
provided.
this<OchainController>.getAssetHistoryById(id: string):
Promise<shim.Iterators.HistoryQueryIterator>
- Parameters:
id : string
– Key used to save data into the ledger.
- Returns:
Promise<shim.Iterators.HistoryQueryIterator>
- Returns a history query iterator. You have to iterate over it.
-
query
- The
query
method will run a Rich SQL/Couch DB query over the ledger. This method is only supported for remote deployment on Oracle Blockchain Platform. This is a generic method for executing SQL queries on the ledger.
this<OchainController>.query(queryStr: string):
Promise<shim.Iterators.StateQueryIterator>
- Parameters:
queryStr : string
- Rich SQL/Couch DB query.
- Returns:
Promise<shim.Iterators.StateQueryIterator>
- Returns a state query iterator. You have to iterate over it.
-
generateCompositeKey
- This method generates and returns the composite key based on the
indexName
and the attributes given in the arguments.
this<OchainController>.generateCompositeKey(indexName: string, attributes:
string[]): string
- Parameters:
indexName : string
- Object Type of the key used to save data into the ledger.
attributes: string[ ]
- Attributes based on which composite key will be formed.
- Returns:
string
- Returns a composite key.
-
getByCompositeKey
- This method returns the asset that matches the key and the column given in the attribute parameter while creating composite key.
indexOfId
parameter indicates the index of the key returned in the array of stub method SplitCompositeKey
. Internally this method calls Hyperledger Fabric’s getStateByPartialCompositeKey
, splitCompositeKey
and getState
.
this<OchainController>.getByCompositeKey(key: string, columns: string[],
indexOfId: number): Promise<any []>
- Parameters:
key: string
– Key used to save data into ledger.
columns: string[ ]
- Attributes based on key is generated.
indexOfId: number
- Index of attribute to be retrieved from Key.
- Returns:
Promise< any [ ]
- Returns any []
on completion.
-
getTransactionId
- Returns the transaction ID for the current chaincode invocation request. The transaction ID uniquely identifies the transaction within the scope of the channel.
this<OchainController>.getTransactionId(): string
- Parameters:
- Returns:
string
- Returns the transaction ID for the current chaincode invocation request.
-
getTransactionTimestamp
- Returns the timestamp when the transaction was created. This is taken from the transaction
ChannelHeader
, therefore it will indicate the client's timestamp, and will have the same value across all endorsers.
this<OchainController>.getTransactionTimestamp(): Timestamp
- Parameters:
id : string
– Key used to save data into the ledger.
- Returns:
Timestamp
- Returns the timestamp when the transaction was created.
-
getTransactionInvoker
- Returns the caller of the transaction from the Transient map property
bcsRestClientId
.
this<OchainController>.getTransactionInvoker(): string
- Parameters:
- Returns:
string
- Returns the caller of the transaction.
-
getChannelID
- Returns the channel ID for the proposal for chaincode to process.
this<OchainController>.getChannelID(): string
- Parameters:
- Returns:
string
- Returns the channel ID.
-
getCreator
- Returns the identity object of the chaincode invocation's submitter.
this<OchainController>.getCreator(): shim.SerializedIdentity
- Parameters:
- Returns:
shim.SerializedIdentity
- Returns identity object.
-
getSignedProposal
- Returns a fully decoded object of the signed transaction proposal.
this<OchainController>.getSignedProposal():
shim.ChaincodeProposal.SignedProposal
- Parameters:
- Returns:
shim.ChaincodeProposal.SignedProposal
- Returns decoded object of the signed transaction proposal.
-
getArgs
- Returns the arguments as array of strings from the chaincode invocation request.
this<OchainController>.getArgs(): string[]
- Parameters:
- Returns:
string [ ]
- Returns arguments as array of strings from the chaincode invocation.
-
getStringArgs
- Returns the arguments as array of strings from the chaincode invocation request.
this<OchainController>.getStringArgs(): string[]
- Parameters:
- Returns:
string [ ]
- Returns arguments as array of strings from the chaincode invocation.
-
getMspID
- Returns the MSP ID of the invoking identity.
this<OchainController>.getMspID(): string
- Parameters:
- Returns:
string
- Returns the MSP ID of the invoking identity.
-
getNetworkStub
- The user can get access to the shim stub by calling
getNetworkStub
method. This will help user to write its own implementation of working directly with the assets.
this<OchainController>.getNetworkStub(): shim.ChaincodeStub
- Parameters:
- Returns:
shim.ChaincodeStub
- Returns chaincode network stub.
Custom Methods
The following custom methods were generated from our example specification file.
The executeQuery
shows how SQL rich queries can be called. The validators against the arguments are added automatically by Blockchain App Builder based on the type of the argument specified in the specification file.
/**
*
* BDB sql rich queries can be executed in OBP CS/EE.
* This method can be invoked only when connected to remote OBP CS/EE network.
*
*/
@Validator(yup.string()}
public async executeQuery(query: string) {
const result = await OchainController.query(query);
return result;
}
@Validator(yup.string(), yup.number()}
public async fetchRawMaterial(supplierId: string, rawMaterialSupply: number) {
}
@Validator(yup.string(), yup.string(), yup.number())
public async getRawMaterialFromSupplier(manufacturerId: string, supplierId: string, rawMaterialSupply: number) {
}
@Validator(yup.string(), yup.number(), yup.number())
public async createProducts(manufacturerId: string, rawMaterialConsumed: number, productsCreated: number) {
}
public async sendProductsToDistribution() {
}
Init Method
We have provided one init
method in the controller with an empty definition. This method will be called by the Hyperledger Fabric Init
method during first time instantiating or upgrading the chaincode.
export class TestTsProjectController extends OchainController {
public async init(params: any) {
return;
}
If you would like to initialize any application state at this point, you can use this method to do that.