Writing Reusable Configuration
This tutorial builds on Getting Started and teaches you how to create reusable workflow components. You’ll write Configuration-as-Code that eliminates duplication by creating parameterised workflow builders that can generate multiple workflow instances from a single class.
Prerequisites
- Completion of the Getting Started tutorial
- Basic understanding of GitHub Actions workflows
- Familiarity with cron expressions (optional)
Step 1: Create a Simple Scheduled Workflow
Let’s start with a basic workflow that prints a greeting message on a schedule. Create a new workflow class:
Better Experience in Landscape Mode
The code examples look much better when viewed horizontally. Please rotate your device for the best experience!
Or view on a larger screen to see the code!
class GreetingWorkflow : Builder<Workflow> {
override fun build() = Workflow("Morning Greeting") {
on += Schedule {
cron += Cron.of("0 9 * * 1-5") // 9 AM UTC, weekdays
}
jobs += Job("greet", UBUNTU_LATEST) {
steps += RunCommand("echo 'Good morning from Typeflows!'")
}
}
}
This workflow runs every morning at 9 AM UTC and prints a greeting message. But what if we want different schedules or messages? We could copy and paste this code, but that’s exactly what Configuration-as-Code helps us avoid.
Step 2: Make It Reusable
Instead of duplicating code, let’s create a parameterised builder that accepts the schedule and message as parameters:
Better Experience in Landscape Mode
The code examples look much better when viewed horizontally. Please rotate your device for the best experience!
Or view on a larger screen to see the code!
class ScheduledGreeting(
private val name: String,
private val cronSchedule: Cron,
private val message: String
) : Builder<Workflow> {
override fun build() = Workflow(name) {
on += Schedule {
cron += cronSchedule
}
jobs += Job("greet", UBUNTU_LATEST) {
steps += RunCommand("echo '$message'")
}
}
}
Now we have a reusable ScheduledGreeting class that can generate different workflows based on the parameters we provide.
Step 3: Reuse Your Builder
Now let’s use our reusable ScheduledGreeting class in our Typeflows. Update your Typeflows class:
Better Experience in Landscape Mode
The code examples look much better when viewed horizontally. Please rotate your device for the best experience!
Or view on a larger screen to see the code!
class Typeflows : Builder<TypeflowsGitHubRepo> {
override fun build() = TypeflowsGitHubRepo {
dotGithub = DotGitHub {
workflows += ScheduledGreeting(
name = "morning-greeting",
cronSchedule = Cron.of("0 9 * * 1-5"),
message = "Good Morning!"
)
}
}
}
Step 4: Export and Verify
Run the export process to generate your workflow files:
Better Experience in Landscape Mode
The code examples look much better when viewed horizontally. Please rotate your device for the best experience!
Or view on a larger screen to see the code!
./gradlew typeflowsExport
Check that the workflow file has been created in your .github/workflows/ folder:
Better Experience in Landscape Mode
The code examples look much better when viewed horizontally. Please rotate your device for the best experience!
Or view on a larger screen to see the code!
name: Morning Greeting
on:
schedule:
- cron: '0 9 * * *' # 9 AM UTC every day
jobs:
greet:
runs-on: ubuntu-latest
steps:
- name: Print greeting
run: echo "Good Morning!"
Conclusion
You now understand the fundamentals of creating reusable workflow components with Typeflows. Your ScheduledGreeting class demonstrates a key principle of Configuration-as-Code: write once, use everywhere, maintain in one place.
With traditional YAML workflows, you can reuse configuration through copy-paste or templating, but you’re doing so in a fundamentally unsafe way - you don’t know if they will work until you run them! Creating variations requires separate files with nearly identical content, manual synchronisation when making changes, and carries the risk of inconsistency and runtime failures.
With Typeflows, you have:
- One builder class that can generate multiple workflow variations
- Consistent behaviour across all instances
- A single point of change for shared logic
- Type-safe parameters that catch configuration errors