Skip to main content

Migration guide: script to compiled definitions

This guide describes how to migrate from GPAL script files (.kts) to compiled Kotlin classes (.kt) that extend the appropriate GPal* base class. Migration is optional: script-based configuration continues to work as before. Moving to compiled definitions gives you full IDE support and refactoring, compile-time safety, configuration in the same codebase and build as the rest of your application, easier testing without script files, and the ability to adopt gradually (script and compiled can coexist).

GPAL types and base classes

For each GPAL type, extend the corresponding base class and use the same DSL inside a lambda. The script file extension is the traditional form; the process <script> tag is optional when you use only compiled definitions.

GPAL typeBase classGradle dependencyScript extensionDocumentation
Data ServerGPalDataserverimplementation(genesis("pal-dataserver"))-dataserver.ktsData Server / Runtime configuration
Event HandlerGPalEventHandlerimplementation(genesis("pal-eventhandler"))-eventhandler.ktsEvent Handler / Runtime configuration
Request/ReplyGPalRequestServerimplementation(genesis("pal-requestserver"))-reqrep.ktsRequest Server / Runtime configuration
ConsolidatorGPalConsolidatorimplementation(genesis("pal-consolidator"))-consolidator.ktsConsolidator / Runtime configuration
PipelineGPalPipelineimplementation(genesis("pal-datapipeline"))-pipelines.ktsData Pipelines / Runtime configuration
StreamerGPalStreamerimplementation(genesis("pal-streamer"))-streamer.ktsStreamer / Runtime configuration
Streamer ClientGPalStreamerClientimplementation(genesis("pal-streamerclient"))-streamer-client.ktsStreamer Client / Runtime configuration

Step-by-step: from script to compiled

  1. Add the module's Gradle dependency — the base class lives in the same PAL module you'd otherwise reference in <module>, so your application module needs it on its compile classpath. See the dependency for each GPAL type in the table above.

  2. Create a Kotlin class in a package that is scanned by your process (e.g. your application's main package).

  3. Extend the appropriate base class (e.g. GPalDataserver, GPalEventHandler) and move the GPAL from your .kts file.

  4. Correct missing imports GPAL scripts by default have default imports, you will need to ensure all classes used are imported.

  5. Ensure the package is scanned — set your process's <package> in processes.xml to the definition's package, or any package above it. Scanning covers subpackages, so <package>com.company.app</package> will still pick up a definition at com.company.app.event.MyEventHandler — it doesn't need to sit directly in com.company.app.

  6. Run the application and confirm behaviour is unchanged.

  7. Remove the script file — once all definitions have been migrated, the <script> entry for that GPAL type in your process definition can be removed, so only the compiled definition is used. If you keep the script file, both are merged.

Imports

Script files receive a set of default imports (e.g. generated DAO, view, and SysDef types) so you can reference them without declaring imports. In compiled Kotlin classes you must add the imports yourself: the base class for your GPAL type (e.g. global.genesis.dataserver.pal.script.GPalDataserver), any tables (from global.genesis.gen.config.tables.* — e.g. CLIENT, DEBTOR, INVOICE), any views (from global.genesis.gen.config.view.*), and any other types used in the DSL. Tables and views are separate packages, and most definitions need both. The GPAL Standards page lists the imports that are available by default in scripts; use that as a reference when writing your compiled class.

Example: Data Server

Script form: myprocess-dataserver.kts

query("ALL_TRADES", TRADE_VIEW) {
config { compression = true }
indices { unique("BY_ID") { TRADE_ID } }
}

Compiled form: MyDataServer.kt

package myapp.definition

import global.genesis.dataserver.pal.script.GPalDataserver
import global.genesis.gen.config.view.TRADE_VIEW

class MyDataServer : GPalDataserver({
// full data server dsl support from here, for example
query("ALL_TRADES", TRADE_VIEW) {
config { compression = true }
indices { unique("BY_ID") { TRADE_ID } }
}
})

The DSL inside the lambda is identical; only the surrounding container (script vs class) changes. Ensure the package definition (or the package that contains this class) is scanned by your process (e.g. if your process already scans myapp, place the class in myapp.definition or add that package to the process scan list).

Mixing script and compiled

You can keep some definitions in script files and add others in compiled classes. Both are merged at runtime; for example, all Data Server queries from scripts and from compiled GPalDataserver implementations are combined. Use this to migrate gradually or to keep a few script-based definitions while moving the rest to compiled.

Migrating one type at a time in a multi-type process

A GENESIS_COMPACT_PROCESS commonly bundles several GPAL types together — for example a Data Server and an Event Handler in one process, sharing one <module>/<package>/<script> set. Migrating gradually means one type in that process becomes compiled while the others stay script-based, which is a different situation from the single-type "mixing" above: it's not one type with both forms, it's one process with different types each fully in one form or the other.

The <module> and <package> tags are each a comma-separated union — list whatever every type in the process still needs, not a fixed one-to-one pairing between the two lists. <script> narrows down to just the .kts files still backing a script-based type. <language>pal</language> stays as long as anything in the process is still script-based (it enables script compilation for the whole process), and is only dropped once every type in that process is compiled.

Migrating a compact process's Data Server first, with its Event Handler still scripted, goes through three stages:

Before — both script-based:

<module>genesis-pal-dataserver,genesis-pal-eventhandler</module>
<package>global.genesis.dataserver.pal,global.genesis.eventhandler.pal</package>
<script>myapp-dataserver.kts,myapp-eventhandler.kts</script>
<language>pal</language>

Intermediate — Data Server compiled, Event Handler still script-based: the Data Server's PAL module and package are replaced with your app's own; the Event Handler's PAL module, package, and script file are untouched; <language> stays, because the Event Handler still needs it.

<module>myapp,genesis-pal-eventhandler</module>
<package>myapp.dataserver,global.genesis.eventhandler.pal</package>
<script>myapp-eventhandler.kts</script>
<language>pal</language>

After — both compiled: your app's module now covers every definition, so it's listed once; <package> unions both definition packages; <script> and <language> are dropped entirely, since nothing in the process is script-based any more.

<module>myapp</module>
<package>myapp.dataserver,myapp.eventhandler</package>

Runtime configuration

When using compiled definitions only, the process definition uses your definition package (e.g. my.app) and your application's module (e.g. position-app). You do not set the script or language tags; dependency injection discovers compiled GPAL definitions in the scanned package and instantiates the service automatically. For full details (including script-based and mixed setups), see Processes and the runtime configuration section in each GPAL type's documentation (e.g. Data Server).

Testing without a script file

testImplementation(genesis("testsupport")) should already be in your app module's build.gradle.kts by default — it's the only test dependency you need, covering GenesisJunit and every GPAL type's test client. There's no separate one per type. A compiled definition can be brought into a GenesisJunit test in either of two ways:

  • @PackageScan adds the definition class's whole package to the scan list; GenesisJunit discovers and constructs every scanned definition in it, exactly as a running process would.
  • @ProvidedInstance registers one exact class (or a pre-built instance) without scanning its package at all — a more surgical choice when you don't want to pull in everything else that package contains.
import global.genesis.testsupport.jupiter.GenesisTest
import global.genesis.testsupport.jupiter.PackageScan
import myapp.definition.MyDataServer
import org.junit.jupiter.api.Test

@GenesisTest
@PackageScan(MyDataServer::class)
class MyDataServerTest {

// inject a client and write tests as usual
}
import global.genesis.testsupport.jupiter.GenesisTest
import global.genesis.testsupport.jupiter.ProvidedInstance
import myapp.definition.MyDataServer
import org.junit.jupiter.api.Test

@GenesisTest
class MyDataServerTest {

@ProvidedInstance
private val myDataServer = MyDataServer::class

// inject a client and write tests as usual
}

@PackageScan takes a KClass, not a package name string, so the class itself — not its location — is what your test declares; renaming or moving MyDataServer keeps the test in sync automatically. @ProvidedInstance accepts a KClass too (Guice constructs and injects it, same as @PackageScan would, just for one exact type) or a pre-built instance if you'd rather construct it yourself. Either annotation is enough on its own; you don't need both. If a process mixes script files and compiled definitions, combine @PackageScan and/or @ProvidedInstance with @TestScriptFile to bring everything into the same test.

For full worked examples, including injecting a client and asserting behaviour, see the testing sections in each GPAL type's documentation (e.g. Data Server testing, Event Handler testing) and the Testing API.

See also

  • GPAL Standards — overview of script and compiled definitions
  • Processes — how the <script> tag works and when it is optional