How to create a trigger and before/after functions

In a series of posts, we have seen how to implement User Defined Functions.

Let’s move on to our next topic : Triggers

We should use trigger to

  • Maintain integrity of data
  • Automate a response to an event
  • Do a validation check

In Hypi, @trigger is a directive supported with before and after semantics.

Quoting from the documentation some important points:

  • A trigger’s before function is executed synchronously. i.e. it will complete before the function it is attached to.
  • A trigger’s after function is executed asynchronously. i.e. the function it is attached to will return before the trigger is completed.
  • If a trigger’s before function returns false, the function it is attached to will not be executed, nor will the after function.

Example

Suppose we have a Student Database and we need to update the marks of the student for various subjects.
While updating the student record we will do a validation check if the record exists. That will be our before function.

After the record gets updated successfully, we will formulate a notification message. The notification can also be sent over to notification services. Here we have just stored the message in a data type for simplicity. This will be our after function.

Here is the schema!

type UpdateNotification {
    id : ID
    updateDate : DateTime
    message: String
}
type StudentInformation {
    name : String
    dob: DateTime
    address: Address
    standard: String
    division: String
    contact: Int
    marks: StudentMarks
}

type Query{
  checkID(sub1:Float,sub2:Float,sub3:Float, hypiId: ID):Boolean @tan(type:Groovy, 
  inline: """ 
        def id = gql(\"""{get(type:StudentInformation,id:"$hypiId"){...on StudentInformation{hypi{id}}}}\""").data.get.hypi.id
        if (id=="$hypiId") {
           return true
        } else {
         return false
        }
    """)
}
type Mutation {
# Trigger Function     
  addStudentMarks(sub1:Float,sub2:Float,sub3:Float, hypiId: ID):Json @tan(type:Groovy, 
  inline: """ 
  return gql(\"""
        mutation {
            upsert(
                values: {
                    StudentInformation: [
                        {  
                            hypi: {id: "$hypiId"},
                            marks: { subject1: $sub1, subject2: $sub2, subject3: $sub3 } 
                        }
                    ]
                }
            ) {
                id
            }
        }
    \"""
    )
    """) @trigger(config: {
    before: {type: Query, field: "checkID"},
    after: {type: Mutation, field: "formNotification"}   
  })

  formNotification(sub1:Float,sub2:Float,sub3:Float, hypiId: ID):Boolean @tan(type:Groovy, 
  inline: """ 
        def date = gql(\"""{get(type:StudentInformation,id:"$hypiId"){...on StudentInformation{hypi{updated}}}}\""").data.get.hypi.updated
        def msg = "The student record with id : '$hypiId' updated"
        gql(\"""
            mutation {
                upsert(
                    values: {
                        UpdateNotification: [
                            {  
                                id:"$hypiId",
                                updateDate:"$date",
                                message: "$msg" 
                            }
                        ]
                    }
                ) {
                    id
                }
            }
        \""")
        return true
      """)
}
  • checkID is the before function that checks if the provided input hypi ID is correct.
  • addStudentMarks function updates the student record.
  • formNotification forms the message for notification. You may actually send the notification through http gateway by Hypi or through serverless function.
  • We are updating already created StudentInformation object or record. Hence using hypi.id to update the record.

Let’s execute the function!

mutation{
  addStudentMarks(sub1:10.0,sub2:10.0,sub3:10.0,hypiId:"01FHA2NZRQK7M2SABRRNZ7E65X")
}
#result
{
  "data": {
    "addStudentMarks": null
  },
  "errors": [
    {
      "message": "Exception while fetching data (/addStudentMarks) : io.hypi.arc.os.gql.HypiGraphQLException: Pre-condition given by Query.checkID failed",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "addStudentMarks"
      ]
    }
  ]
}

before function fails if the record doesn’t exist.

mutation{
  addStudentMarks(sub1:10.0,sub2:10.0,sub3:10.0,hypiId:"01FSA2NZRQK7M2SABRRNZ7E65X")
}
#result
{
  "data": {
    "addStudentData": [
      {
        "__typename": "Hypi",
        "id": "01FSA2NZRQK7M2SABRRNZ7E65X",
        "impl": null,
        "created": "2022-01-13T16:00:11Z",
        "updated": "2022-01-13T16:00:11Z",
        "trashed": null,
        "createdBy": "01FQDY5XDRQPERKPCWAWYDFV7W",
        "instanceId": "01FQDYDP8681299EXZBWJXRX2Y",
        "tags": null
      }
    ]
  }
}
#UpdateNotification Data
{
  "data": {
    "find": {
      "edges": [
        {
          "node": {
            "hypi": {
              "id": "01FSA3EDXADX1XC48EKWG9098N"
            },
            "id": "01FSA2NZRQK7M2SABRRNZ7E65X",
            "updateDate": "2022-01-13T16:13:31Z",
            "message": "The student record with id : '01FSA2NZRQK7M2SABRRNZ7E65X' updated"
          },
          "cursor": "01FSA3EDXADX1XC48EKWG9098N"
        }
      ]
    }
  }
}