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
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 theafter
function.
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"
]
}
]
}
-
functiontrigger1
is successfully executed asbefore
function (truereturn
) returns true.After
function (truereturn
) also returns true. -
functiontrigger2
fails as before functions (falsereturn
) fails. You can see the error message herePre-condition given by Mutation.falsereturn failed
. Asbefore
function returns false, the function does not get executed. -
functiontrigger3
is successfully executed asbefore
function (truereturn
) returns true. However,after
function 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"
]
}
]
}