Which hook when?
| Need | Hook |
|---|---|
| Check or default data every time an entity is saved | Pre-update or validation rules |
| Decide who gets new work | Assignment (and segmentation) rules |
| React to a change by notifying another system | Event fired rules + messaging (module 16) |
| Replace how the app does one specific thing (number generation, policy search, rating, billing system) | Plugin |
| Process many records on a schedule | Batch process or work queue |
Rule sets
Rules are Gosu, organised in Studio as a tree: rule set categories → rule sets → rules, each rule having a condition and an action, and optional child rules. The app calls a rule set at a fixed moment, passing in the root entity.
| Rule set type | Runs | Typical use |
|---|---|---|
| Pre-update | When a bundle commits, before validation | Default and derive values, create activities |
| Validation | When a bundle commits (and on demand) | Reject invalid data at a validation level |
| Segmentation (CC) | On new claims and exposures | Set complexity segment and strategy |
| Assignment | When work needs an owner | Pick group, then user |
| Workplan (CC) | After assignment of new claims | Create initial activities |
| Event fired | When an entity event fires for a messaging destination | Create integration messages |
A validation rule, written as condition and action:
// Rule: Loss date cannot be in the future
// CONDITION
claim.LossDate != null and claim.LossDate > gw.api.util.DateUtil.currentDate()
// ACTION
claim.reject(ValidationLevel.TC_LOADSAVE,
DisplayKey.get("Ext.Validation.LossDateInFuture"),
null, null)
- Rules run top to bottom. An action can call
actions.exit()to stop the rest of the rule set — common in assignment once an owner is found. - Validation levels let data be incomplete early (load-save) but require more before later steps, e.g. "ready to pay" or "sent to external system".
- Keep conditions cheap: they run on every commit. Avoid queries in pre-update rules unless they're scoped tightly.
Put the real logic in a Gosu class and have the rule call it in one line. Then GUnit tests can call the class directly, and rule trees stay readable for BAs reviewing them.
Plugins
A plugin is an interface the application calls when it needs something done — and which implementation runs is configured, not hard-coded. Each app ships with plugin interfaces such as number generators, policy search (ClaimCenter), billing system and policy system links (PolicyCenter ↔ BillingCenter), document storage and rating. Exact names vary by app and release.
- The plugin registry (
.gwpfiles) maps each interface to an implementation class and its parameters. - Base often ships a demo or internal implementation; projects replace it with one that calls their real systems.
- Parameters in the registry (URLs, timeouts) can differ per environment.
You can also define your own plugin interface, so callers don't care which vendor sits behind it:
package acme.fraud
interface IFraudScoringPlugin_Ext {
function score(claim : Claim) : int
}
class VendorFraudScoringPlugin_Ext implements IFraudScoringPlugin_Ext {
override function score(claim : Claim) : int {
var response = FraudApiClient_Ext.instance().check(claim.PublicID, claim.LossCause.Code)
return response.Score
}
}
// Caller: look the plugin up through the registry, not with "new"
var plugin = gw.plugin.Plugins.get(IFraudScoringPlugin_Ext)
claim.FraudScore_Ext = plugin.score(claim)
Batch processes and work queues
Background work runs in two styles:
Batch process
A single job that does its work start to finish: e.g. renewals start, invoice billing, activity escalation. Simple, but runs on one server thread.
Work queue
A writer finds the items to process; many workers on many servers process them in parallel, each item in its own transaction. Use for high volumes.
Both are scheduled through the scheduler configuration and can be run on demand from the server tools screens. A minimal custom batch process:
package acme.batch
uses gw.processes.BatchProcessBase
class CloseStaleDraftClaims_Ext extends BatchProcessBase {
construct() {
super(BatchProcessType.TC_CLOSESTALEDRAFTCLAIMS_EXT)
}
override function doWork() {
for (claim in StaleDraftClaimFinder_Ext.find(30)) {
if (TerminateRequested) return
gw.transaction.Transaction.runWithNewBundle(\ bundle -> {
bundle.add(claim).markAbandoned_Ext()
})
incrementOperationsCompleted()
}
}
}
- Register a new
BatchProcessTypetypecode for it, and add it to the process registry and scheduler. - Check
TerminateRequestedso operators can stop it cleanly. - Commit in small bundles so one bad record doesn't roll back the whole run.
Common pitfalls
- Doing integration calls inside pre-update rules — the commit waits on a remote system and fails if it's down. Use messaging instead.
- Pre-update rules that change data which triggers more rules, causing loops or surprising side effects.
- Assignment rules that fall through without assigning anyone; always have a default.
- Batch jobs that load everything into memory instead of streaming query results.