Triggers
In many databases and some other systems, it is possible to define a trigger that is executed before or after an event has happened. A trigger is used to maintain the integrity of the data. Or it can be used to automate a response to any event. For example, sending a welcome email when a new customer adds to the database, validating the data before creating an entry, etc.
In Hypi, triggers are supported with both a before and after semantics by adding the @trigger directive to a function.
The before function gets executed before the function being called. It can be used for validation or calling other services. If it fails, it prevents the execution of the function being called.
The after function gets executed after the called function. It cannot affect the results of the function being called. It can be used to notify another service of an operation being compelted.
Important points to remember
-
Triggers can break existing functions if you decide to completely replace the output. You may override the existing functions using Trigger.
-
A trigger's
beforefunction is executed synchronously. i.e. it will complete before the function it is attached to. -
A trigger's
afterfunction is executed asynchronously. i.e. the function it is attached to will return before the trigger is completed. -
If a trigger's
beforefunction returns false, the function it is attached to will not be executed, nor will theafterfunction.
Let’s go through the below schema to create a trigger and before/after functions:
type AfterResult {
check: String
}
type Query {
# Before/After function
truereturn:Boolean @tan(type:Groovy, inline: "return true")
# Trigger Function 1
functiontrigger1:String @tan(type:Groovy,
inline: "return 'Trigger Successful!'") @trigger(config: {
before: {type: Query, field: "truereturn"},
after: {type: Query,field: "truereturn"}
})
# Trigger Function 2
functiontrigger2(trigname:String):String @tan(type:Groovy,
inline: "return 'Trigger Successful'") @trigger(config: {
before: {type: Mutation, field: "falsereturn"},
})
# Trigger Function 3
functiontrigger3(trigname:String):String @tan(type:Groovy,
inline: "return 'Trigger Successful'") @trigger(config: {
before: {type: Query, field: "truereturn"},
after: {type: Mutation,field: "falsereturn"}
})
}
type Mutation {
#Before/After function
falsereturn(trigname:String):Boolean @tan(type:Groovy, inline:"""
gql(\"""
mutation {
upsert(values: {
AfterResult: {
check: "'$trigname' Failed"
}
}
) {
id
}
}
\""")
return false
""")
}
Example
In the above schema, three functions are declared to which trigger is attached using the @trigger directive. (functiontrigger1,functiontrigger2,functiontrigger3). truereturn and falsereturn functions work as before/after functions. Trigger functions and before/after functions are implemented just like User Defined Functions.
For functiontrigger1, before and after functions return ‘true’. For functiontrigger2, before function fails. And for functiontrigger3 before function returns true, but after function fails.
Let’s execute the functions!
- GraphQL Query
- Response
query {
functiontrigger1
functiontrigger2(trigname:"Before Function")
functiontrigger3(trigname:"After Function")
}
{
"data": {
"functiontrigger1": "Trigger Successful!",
"functiontrigger2": null,
"functiontrigger3": "Trigger Successful"
},
"errors": [
{
"message": "Exception while fetching data (/functiontrigger2) : io.hypi.arc.os.gql.HypiGraphQLException: Pre-condition given by Mutation.falsereturn failed",
"locations": [
{
"line": 3,
"column": 3
}
],
"path": [
"functiontrigger2"
]
}
]
}
-
functiontrigger1is successfully executed asbeforefunction (truereturn) returns true.Afterfunction (truereturn) also returns true. -
functiontrigger2fails as before functions (falsereturn) fails. You can see the error message herePre-condition given by Mutation.falsereturn failed. Asbeforefunction returns false, the function does not get executed. -
functiontrigger3is successfully executed asbeforefunction (truereturn) returns true. However,afterfunction returns false. But this does not affect the execution of the trigger functionfunctiontrigger3.
AfterResult object holds the name of the function (before/after) which failed. Let's verify the objects using find function.
- GraphQL Query
- Response
{
find(type: AfterResult, arcql: "*") {
edges {
node {
... on AfterResult {
check
}
}
cursor
}
}
}
{
"data": {
"find": {
"edges": [
{
"node": {
"check": "'Before Function' Failed"
},
"cursor": "01F2RCK09MKCG58W2W54CMCQK2"
},
{
"node": {
"check": "'After Function' Failed"
},
"cursor": "01F2RCK0D17HV9JSE79KPG4JXJ"
}
]
}
}
}
You can see that 'Before Function' of functiontrigger2 got executed and it returned false. Hence the Trigger was unsuccessful.
'After Function' of functiontrigger3 failed. It indicates that After function got executed and failed but still Trigger was successful.
We have added AfterResult just for demonstration puropse. It indicates how After/Before function can be implemented.
Function overriding
The in-built function of Hypi can be overriden using Trigger. Function overriding is allowed so long as all parameters of the new function exactly match the original parameters. This can be used to arbitrarily allow or deny access to a functionality depending upon the event to be handled by your App.
Care must be taken when overriding functions. upsert for example is used everywhere - breaking it will cause many things to fail.
Let's override built-in upsert function using @trigger . You may allow or disallow insertion of data by overriding this function. Please check below schema.
type Query {
allow(values: HypiUpsertInputUnion!)
:Boolean @tan(type:Groovy,inline: "return true")
disallow(values: HypiUpsertInputUnion!)
:Boolean @tan(type:Groovy, inline: "return false")
}
type Mutation {
after(values: HypiUpsertInputUnion!)
: Boolean @tan(type: Groovy, inline: """
gql('''
mutation Create($values: HypiUpsertInputUnion!){
upsert(values: $values) {
hypi{id}
}
}''', new java.util.LinkedHashMap())
return true
""")
upsert(values: HypiUpsertInputUnion!):[Hypi!] @trigger(config: {
before:{type: Query, field: "allow"},
after:{type: Mutation, field: "after"}
})
}
If you insert the data, the upsert function works as before. The data gets added into the object successfully. However, if you set before function to disallow, the insertion of data fails as the disallow function returns false.
- GraphQL Query
- Response
mutation {
upsert(values: {
Email: { value: "[email protected]" } }
) {
id
}
}
{
"data": {
"upsert": null
},
"errors": [
{
"message": "Exception while fetching data (/upsert) : io.hypi.arc.os.gql.HypiGraphQLException: Pre-condition given by Query.disallow failed",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"upsert"
]
}
]
}