Blockchain App Builder takes the input from your specification file and generates a fully-functional scaffolded chaincode project.
If the chaincode project is in the Go language, the scaffolded project contains three main files:
main.go
<chaincodeName>.model.go
<chaincodeName>.controller.go
All the necessary libraries are installed and packaged.
The <chaincodeName>.model.go
contains multiple asset definitions and <chaincodeName>.controller.go
contains the asset's behavior and CRUD methods. The various Go struct tags and packages in model.go
and controller.go
provide support for features like automatic validation of arguments, marshalling/unmarshalling of arguments, transparent persistence capability (ORM) and calling rich queries.
The scaffolded project can be found in $GOPATH/src/example.com/<chaincodeName>
Validators
-
Id
id:"true"
- This validator identifies the property which uniquely defines the underlying model. The asset is saved by the value in this key. This validator automatically applies when a new Go project is scaffolded.
- In the below screenshot
"SupplierId"
is the key for the supplier asset and has a tag property id:"true"
for the SupplierId property.type Supplier struct {
Supplierld string 'json:"Supplierld" validate:"string,mandatory" id:"true"'
RawMaterialAvailable int 'json:"RawMaterialAvailable" validate:"int,min=0"'
License string 'json:"License" validate:"string,min=10"'
ExpiryDate date.Date 'json:"ExpiryDate" validate:"date,before=2020-06-26"'
Active bool 'json:"Active" validate:"bool" default :"true"'
Metadata interface{} 'json:"Metadata,omitempty"'
}
-
Derived
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.
type Supplier struct{
AssetType string 'json:"AssetType" final:"chaincode1.Supplier"'
SupplierId string 'json:"SupplierId" validate:"string" id:"true" mandatory:"true" derived:"strategy=hash,algorith=sha256,format=IND%1%2,License,Name"'
Name string 'json:"Name" validate:"string,min=2,max=4"'
License string 'json:"License" validate:"string,min=2,max=4"'
}
-
Mandatory
validate:"mandatory"
- This marks the following property as mandatory and cannot be skipped while saving to the ledger. If skipped it throws an error. In the below example,
"SupplierId"
has a validate:"mandatory"
tag.Type Supplier struct {
Supplierld string 'json:"Supplierld" validate:"string,mandatory" id:"true"'
RawMaterialAvailable int 'json:"RawMaterialAvailable" validate:"int,min=0"'
License string 'json:"License" validate:"string,min=10"'
ExpiryDate date.Date 'json:"ExpiryDate" validate:"date,before=2020-06-26"'
Active bool 'json:"Active" validate:"bool" default :"true"'
Metadata interface{} 'json:"Metadata,omitempty"'
}
-
Default
default:"<param>"
- This states that the following property can have a default value. The default value in the default tag is used when the property is skipped while saving to the ledger. In the below example property,
Active
has a default value of true
, provided as tag default:"true"
Type Supplier struct {
Supplierld string 'json:"Supplierld" validate:"string,mandatory" id:"true"'
RawMaterialAvailable int 'json:"RawMaterialAvailable" validate:"int,min=0"'
License string 'json:"License" validate:"string,min=10"'
ExpiryDate date.Date 'json:"ExpiryDate" validate:"date,before=2020-06-26"'
Active bool 'json:"Active" validate:"bool" default :"true"'
Metadata interface{} 'json:"Metadata,omitempty"'
}
-
Validate types
- Basic Go types are validated for a property by defining a validate tag. These are the validate tags based on types:
- string:
validate: "string"
- date:
validate: "date"
- number:
validate: "int"
- boolean:
validate: "bool"
-
Min validator
validate:"min=<param>"
- Using the min validator, minimum value can be set for a property of type number and string.
- For type int: In the example,
RawMaterialAvailable
property has a minimum value of 0 and if a value less than 0 is applied to RawMaterialAvailable
an error will be returned.
- For type string: For the string type minimum validator will check the length of the string with the provided value. Therefore, in the below example the
License
property has to be minimum 10 characters long.
- Example:
Type Supplier struct {
Supplierld string 'json:"Supplierld" validate:"string,mandatory" id:"true"'
RawMaterialAvailable int 'json:"RawMaterialAvailable" validate:"int,min=0"'
License string 'json:"License" validate:"string,min=10"'
ExpiryDate date.Date 'json:"ExpiryDate" validate:"date,before=2020-06-26"'
Active bool 'json:"Active" validate:"bool" default :"true"'
Metadata interface{} 'json:"Metadata,omitempty"'
}
-
Max validator
validate:"max=<param>"
- Using the max validator, the maximum value can be set for a property of type number and string.
- For type int: Like the min validator, for type int, if a value provided for the
structfield
is greater than the value provided in the validator then an error will be returned.
- For type string: Like the min validator, max validator will also check the length of the string with given value. In the example, the
Domian
property has a maximum value of 50, so if the Domain
property has a string length more than 50 characters, then an error message will be returned.
type Retailer struct {
Retailerld string 'json:"Retailerld" validate:"string,mandatory" id:"true"'
ProductsOrdered int 'json:"ProductsOrdered" validate:"int,mandatory"'
ProductsAvailable int 'json:"ProductsAvailable" validate:"int" default:"1"'
ProductsSold int 'json:"ProductsSold" validate:"int"'
Remarks string 'json:"Remarks" validate:"string" default :"open for business"'
Items []int 'json:"Items" validate:"array=int,range=l-5"'
Domain string 'json:"Domain" validate:"url,min=30,max=50"'
Metadata interface{} 'json:"Metadata,omitempty"'
}
-
Date validators
- Before validator:
validate:"before=<param>"
- The before validator validates a property of type
date
to have a value less than the specified in parameter.
- In this example, the
ExpiryDate
property should be before "2020-06-26"
and if not it will return an error.Type Supplier struct {
Supplierld string 'json:"Supplierld" validate:"string,mandatory" id:"true"'
RawMaterialAvailable int 'json:"RawMaterialAvailable" validate:"int,min=0"'
License string 'json:"License" validate:"string,min=10"'
ExpiryDate date.Date 'json:"ExpiryDate" validate:"date,before=2020-06-26"'
Active bool 'json:"Active" validate:"bool" default :"true"'
Metadata interface{} 'json:"Metadata,omitempty"'
}
- After validator:
validate:"after=<param>"
- The before validator validates a property of type
date
to have a value greater than the specified in parameter.
- In this example, the
CompletionDate
property should be after "2020-06-26"
and if not it will return an error.Type Supplier struct {
Manufacturerld string 'json:"Manufacturerld" validate:"string,mandatory" id:"true"'
RawMaterialAvailable int 'json:"RawMaterialAvailable" validate:"int,max=8"'
ProductsAvailable int 'json:"ProductsAvailable" validate:"int"'
CompletionDate date.Date 'json:"CompletionDate" validate:"date,after=2020-06-26"'
Metadata interface{} 'json:"Metadata,omitempty"'
}
-
URL validator
validate:"url"
- The URL validator will validate a property for URL strings.
- In this example, the
Domain
property has to be a valid URL.type Retailer struct {
Retailerld string 'json:"Retailerld" validate:"string,mandatory" id:"true"'
ProductsOrdered int 'json:"ProductsOrdered" validate:"int,mandatory"'
ProductsAvailable int 'json:"ProductsAvailable" validate:"int" default:"1"'
ProductsSold int 'json:"ProductsSold" validate:"int"'
Remarks string 'json:"Remarks" validate:"string" default :"open for business"'
Items []int 'json:"Items" validate:"array=int,range=l-5"'
Domain string 'json:"Domain" validate:"string,url,min=30,max=50"'
Metadata interface{} 'json:"Metadata,omitempty"'
}
-
Regexp validator
validate:"regexp=<param>"
- Regexp validator will validate property for the input regular expression.
- In this example, the
PhoneNumber
property will validate for a mobile number as per the regular expression.type Customer struct {
Customerld string 'json:"Customerld" validate:"string,mandatory" id:"true"'
Name string 'json:"Name" validate:"string,mandatory"'
ProductsBought int 'json:"ProductsBought" validate:"int"'
OfferApplied int 'json:"OfferApplied" validate :"int,nax=0"'
PhoneNumber string 'json:"PhoneNumber" validate:"string,regexp=A\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$"'
Received bool 'json:"Received" validate:"bool“'
Metadata interface{} 'json:"Metadata,omitempty"'
}
-
Multiple validators
- Multiple validators can be applied a property.
- In this example, the
Domain
property has validation for a string, URL, and min and max string length.type Retailer struct {
Retailerld string 'json:"Retailerld" validate:"string,mandatory" id:"true"'
ProductsOrdered int 'json:"ProductsOrdered" validate:"int,mandatory"'
ProductsAvailable int 'json:"ProductsAvailable" validate:"int" default:"1"'
ProductsSold int 'json:"ProductsSold" validate:"int"'
Remarks string 'json:"Remarks" validate:"string" default :"open for business"'
Items []int 'json:"Items" validate:"array=int,range=l-5"'
Domain string 'json:"Domain" validate:"string,url,min=30,max=50"'
Metadata interface{} 'json:"Metadata,omitempty"'
}
Model
Asset Type Property
By default every struct will have an additional property called
AssetType
. This property can be useful in fetching only assets of this type. Any changes to this property is ignored during create and update of asset. The property value by default is
<chaincodeName>.<modelName>
.
type Supplier struct {
AssetType string 'json:"AssetType" default:"TestGoProject.Supplier"'
SupplierId string 'json:"SupplierId" validate:"string,mandatory" id:"true'
RawMaterialAvailable int 'json:"RawMaterialAvailable" validate:"int,min=0"'
License string 'json:"License" validate:"string,min=10"'
ExpiryDate date.Date 'json:"ExpiryDate" validate:"date,before=2020-06-26"'
Active bool 'json:"Active" validate:"bool" default:"true"'
Metadata interface{} 'json:"Metadata,omitempty"'
}
ORM
Go chaincodes implement Transparent Persistence Capability (ORM) with the model package.
The following ORM methods are exposed via the model package:
-
model.Get
- Queries the ledger for the stored asset based on the given ID.
func Get(Id string, result ...interface{}) (interface{}, error)
- Parameters:
Id
- The ID of the asset which is required from the ledger.
result (interface{})
- This is an empty asset object of a particular type, which is passed by reference. This object will contain the result from this method. To be used only if type-specific result is required.
asset (interface)
- Empty asset object, which is passed by reference. This object will contain the result from this method. To be used only if type-specific result is required.
- Returns:
interface {}
- Interface contains the asset in the form of map[string]interface{}
. Before operating on this map, it is required to assert the obtained interface with type map[string]interface{}
. To convert this map into an asset object, you can use the utility API util.ConvertMaptoStruct
(see: Utility Package).
error
- Contains an error if returned, or is nil.
-
model.Update
- Updates the provided asset in the ledger with the new values.
func Update(args ...interface{}) (interface{}, error)
- Parameters:
obj (interface)
- The object that is required to be updated in the ledger is passed by reference into this API with the new values. The input asset is validated and verified according to the struct tags mentioned in the model specification and then stored into the ledger.
- Returns:
interface{}
- The saved asset is returned as an interface.
error
- Contains an error if returned, or is nil.
-
model.Save
- Saves the asset to the ledger after validating on all the struct tags.
func Save(args ...interface{}) (interface{}, error)
- Parameters:
obj/args[0] (interface{})
- The object that needs to be stored in the ledger is passed by reference in this utility method.
metadata/args[1] (interface{})
- This parameter is optional. It has been given in order to facilitate you if you're required to store any metadata into the ledger along with the asset at the runtime. This parameter can be skipped if no such requirement exists.
- Returns:
interface {}
- The asset is returned as an interface.
error
- Contains an error if returned, or is nil.
-
model.Delete
- Deletes the asset from the ledger.
func Delete(Id string) (interface{}, error)
- Parameters:
id (string)
- The ID of the asset which is required to be deleted from the ledger.
- Returns:
interface {}
- Contains the asset being deleted in the form of map[string]interface{}
.
-
model.GetByRange
- Returns the list of assets by range of IDs.
func GetByRange(startKey string, endKey string, asset ...interface{})
([]map[string]interface{}, error)
- Parameters:
startkey (string)
- Starting ID for the range of objects which are required.
endkey (string)
- End of the range of objects which are required.
asset interface
- (optional) Empty array of assets, which is passed by reference. This array will contain the result from this method. To be used if type-specific result is required.
- Returns:
[]map[string]interface{}
- This array contains the list of assets obtained from the ledger. You can access the objects iterating over this array and asserting the objects as map[string]interface{}
and using utility to convert to asset object.
error
- Contains an error if returned, or is nil.
-
model.GetHistoryById
- Returns the history of the asset with the given ID.
func GetHistoryByID(Id string) ([]interface{}, error)
- Parameters:
Id (string)
- ID of the asset for which the history is needed.
- Returns:
[]interface{}
- This slice contains the history of the asset obtained from the ledger in form of slice of map[string]interface{}
. You can access each history element by iterating over this slice and asserting the objects as map[string]interface{}
and using utility to convert to asset object.
error
- Contains the error if observed.
-
model.Query
- The query method will run a 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.
func Query(queryString string) ([]interface{}, error)
- Parameters:
queryString (string)
- Input the query string.
- Returns:
[]interface{}
- This will contain the output of the query. The result is in form of slice of interfaces. You need to iterate over the slice and use the elements by converting them to proper types.
error
- Contains the error if observed.
Composite Key Methods
-
model.GenerateCompositeKey
- This method generates and returns the composite key based on the
indexName
and the attributes given in the arguments.
func GenerateCompositeKey(indexName string, attributes []string)
(string, error)
- Parameters:
indexName (string)
- Object type of the composite key.
attrbutes ([]string)
- Attributes of the asset based on which the composite key has to be formed.
- Returns:
string
- This contains the composite key result.
error
- Contains the error if observed.
-
model.GetByCompositeKey
- This method returns the asset that matches the key and the column given in the parameters. The
index
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
.
func GetByCompositeKey(key string, columns []string, index int)
(interface{}, error)
- Parameters:
key (string)
- Object type provided while creating composite key.
column ([]string)
- This is the slice of attributes on which the ledger has to be queried using the composite key.
index(int)
- Index of the attribute.
- Returns:
Interface{}
- Contains the list of assets which are result of this method.
error
- Contains any errors if present.
Stub Method
-
model.GetNetworkStub
- This method will return the Hyperledger Fabric
chaincodeStub
.
- You can get access to the shim stub by calling the
GetNetworkStub
method. This will help you write your own implementation working directly with the assets.
func GetNetworkStub() shim.ChaincodeStubInterface
- Parameters:
- Returns:
shim.ChaincodeStubInterface
- This is the Hyperledger Fabric chaincode stub.
Other Methods
model.GetTransactionId()
model.GetTransactionTimestamp()
model.GetChannelID()
model.GetCreator()
model.GetSignedProposal()
model.GetArgs()
model.GetStringArgs()
model.getId
-
model.GetTransactionId
- Returns the transaction ID for the current chaincode invocation request. The transaction ID uniquely identifies the transaction within the scope of the channel.
func GetTransactionId() string
- Parameters:
- Returns:
string
- This contains the required transaction ID.
-
model.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.
func GetTransactionTimestamp() (*timestamp.Timestamp, error)
- Parameters:
- Returns:
timestamp.Timestamp
- Contains the timestamp required.
error
- Contains any errors if present.
-
model.GetChannelID
- Returns the channel ID for the proposal for the chaincode to process.
func GetChannelID() string
- Parameters:
- Returns:
string
- Contains the required channel ID as a string.
-
model.GetCreator
- Returns the identity object of the chaincode invocation's submitter
func GetCreator() ([]byte, error)
- Parameters:
- Returns:
[ ]byte
- Contains the required identity object serialized.
error
- Contains any errors if present.
-
model.GetSignedProposal
- Returns a fully decoded object of the signed transaction proposal.
func GetSignedProposal() (*peer.SignedProposal, error)
- Parameters:
- Returns:
*peer.SignedProposal
- Contains the required signed proposal object.
error
- Contains any errors if present.
-
model.GetArgs
- Returns the arguments as array of strings from the chaincode invocation request.
func GetArgs() [][]byte
- Parameters:
- Returns:
[ ][ ]byte
- Contains the arguments passed.
-
model.GetStringArgs
- Returns the arguments intended for the chaincode Init and Invoke as a string array.
func GetStringArgs() []string
- Parameters:
- Returns:
[]string
- Contains the required arguments as a string array.
-
model.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:
func (t *Controller) CustomGetterForSupplier(License string, Name string)(interface{}, error){
var asset Supplier
asset.License = License
asset.Name = Name
id,err := model.GetId(&asset)
if err !=nil {
return nil, fmt.Errorf("error in getting ID %s", err.Error())
}
return t.GetSupplierById(id)
}
Utility Package
The following methods in the utility package may be useful:
-
Util.CreateModel
- Parses the provided JSON string and creates an asset object of the provided type.
func CreateModel(obj interface{}, inputString string) error
- Parameters:
inputString (string)
- The input JSON string from which the object is to be created.
obj (interface{})
- The reference of the object that is to be created from the JSON string. This object will store the created model which is also validated as per validator tags.
- Returns:
error
- Contains any errors found while creating or validating the asset.
-
util.ConvertMapToStruct
- Convert the provided map into object of provided type.
func ConvertMapToStruct(inputMap map[string](interface{}), resultStruct
interface{}) error
- Parameters:
inputMap (map[string](interface{}))
- Map which needs to be converted into the asset object.
resultStruct (interface{})
- The reference of the required asset object which needs to be generated from the map. Contains the result asset object required.
- Returns:
error
- Contains any errors found while creating or validating the asset.
Controller
The Controller.go
file implements the CRUD and custom methods for the assets.
You can create any number of classes, functions, or files, but only those methods that are defined on chaincode struct 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:
//
//Supplier
//
func (t *ChainCode) CreateSupplier(inputString string) (interface{}, error) {
var obj Supplier
err := util.CreateModel(&obj, inputString)
if err != nil {
return nil, err
}
return model.Save(&obj)
}
func (t *ChainCode) GetSupplierById(id string) (interface{}, error) {
asset, err := model.Get(id)
return asset, err
}
func (t *ChainCode) UpdateSupplier(inputString string) (interface{}, error) {
var obj Supplier
err := util.CreateModel(&obj, inputstring)
if err != nil {
return nil, err
}
return model.Update(&obj)
}
func (t *ChainCode) DeleteSupplier(id string) (interface{}, error) {
return model.Delete(id)
}
func (t *ChainCode) GetSupplierHistoryById(id string) (interface{}, error) {
historyArray, err := model.GetHistoryByld(id)
return historyArray, err
}
func (t *ChainCode) GetSupplierByRange(startkey string, endKey string) (interface{}, error) {
assetArray, err := model.GetByRange(startkey, endKey)
return assetArray, err
}
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.
You can implement the functionality as per the business logic.
//
//Custom Methods
//
/*
* 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.
*/
func (t *ChainCode) ExecuteQuery(inputQuery string) (interface{}, error) {
resultArray, err := model.Query(inputQuery)
return resultArray, err
}
func (t *ChainCode) FetchRawMaterial(supplierId string, rawMaterialSupply int) (interface{}, error) {
return nil, nil
}
func (t *ChainCode) GetRawMaterialFromSupplier(manufacturerId string, supplierId string, rawMaterialSupply int) (interface{} error) {
return nil, nil
}
Func (t *ChainCode) CreateProducts(manufacturerId string, rawMaterialConsumed int, productsCreated int) (interface{}, error) {
return nil, nil
}
func (t *ChainCode) SendProductsToDistribution() (interface{}, error) {
return nil, nil
}
For Go chaincodes, every custom method should return two values:
empty interface,
error. For example:
func (t *Controller) FetchRawMaterial(supplierId string, rawMaterialSupply int) (interface{}, error) {
return nil, nil
}
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 instantiation or upgrade of a chaincode.
type Controller struct {
}
func (t *Controller) Init(args string) (interface{}, error)
{ return nil, nil
}
If you would like to initialize any application state at this point, you can use this method to do that.