Kotlin + Spring Boot + Temporal Setup

Lesson 2: Building Your Foundation

🧠 Objective: Set up Temporal with Spring Boot + Kotlin, understand how it fits together, and get ready to build real workflows.


βš™οΈ Temporal SDK Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Temporal Server │◄──►│ WorkflowClient   │◄──►│ Your Applicationβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β–²                       β–²                       β–²
         β”‚                       β”‚                       β”‚
         β”‚              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β”‚
         └─────────────►│ Worker           β”‚β—„β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Quick Architecture Breakdown

πŸ” Core Components:

  • WorkflowServiceStubs: Connects to Temporal server (think: Ethernet cable)
  • WorkflowClient: Interface to start workflows (like your remote control)
  • WorkerFactory: Manages the lifecycle of your workers
  • Worker: Executes workflows and activities

πŸ”Œ Spring Boot Integration

@Configuration
class TemporalConfig {
    @Bean
    fun workflowServiceStubs() = WorkflowServiceStubs.newLocalServiceStubs()

    @Bean
    fun workflowClient(stubs: WorkflowServiceStubs) =
        WorkflowClient.newInstance(stubs)

    @Bean
    fun workerFactory(client: WorkflowClient) =
        WorkerFactory.newInstance(client)
}

Simple and clean Spring configuration!


πŸͺ Lifecycle Hooks

@PostConstruct
fun startWorker() = workerFactory.start()

@PreDestroy
fun shutdown() = workerFactory.shutdown()

Why This Matters:

  • βœ… Proper startup - Workers start after beans are initialized
  • βœ… Graceful shutdown - Clean cleanup when app stops
  • βœ… Spring lifecycle - Integrates with Spring Boot lifecycle

πŸ“¦ Task Queues = Workflow Channels

val worker = workerFactory.newWorker("my-task-queue")
val stub = client.newWorkflowStub(
    MyWorkflow::class.java,
    WorkflowOptions.newBuilder()
        .setTaskQueue("my-task-queue")
        .build()
)

Why Task Queues Matter:

  • βœ… Horizontal scaling - Add more workers as needed
  • βœ… Clear separation - Different queues for different responsibilities
  • βœ… Logical routing - Route workflows to appropriate workers

πŸ§ͺ Local vs Production Setup

Local Development:

WorkflowServiceStubs.newLocalServiceStubs()

Production:

WorkflowServiceStubs.newServiceStubs(
    WorkflowServiceStubsOptions.newBuilder()
        .setTarget("temporal.mycompany.com:7233")
        .build()
)

πŸ”§ Spring Configuration Example

# application.properties
temporal.server.host=localhost
temporal.server.port=7233
temporal.namespace=default
@ConfigurationProperties(prefix = "temporal")
data class TemporalProperties(
    val server: Server = Server()
) {
    data class Server(
        val host: String = "localhost",
        val port: Int = 7233
    )
}

πŸ“š Dependencies You'll Need

// build.gradle.kts
implementation("io.temporal:temporal-sdk:1.22.3")
implementation("io.temporal:temporal-kotlin:1.22.3")
testImplementation("io.temporal:temporal-testing:1.22.3")

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
implementation("io.github.microutils:kotlin-logging:3.0.5")

πŸ“ Pro tip:

Lock versions and group Temporal dependencies together for clarity.


🧠 Best Practices

πŸ”„ Environment-Specific Beans

@Profile("test")
@Bean 
fun testStubs() = WorkflowServiceStubs.newLocalServiceStubs()

@Profile("!test")
@Bean 
fun prodStubs() = WorkflowServiceStubs.newServiceStubs(...)

Result: Different configurations for different environments


🧡 Multiple Workers Example

@PostConstruct
fun startWorkers() {
    val userWorker = workerFactory.newWorker("user-queue")
    val orderWorker = workerFactory.newWorker("order-queue")

    userWorker.registerWorkflowImplementationTypes(UserWorkflowImpl::class.java)
    orderWorker.registerActivitiesImplementations(OrderActivitiesImpl())

    workerFactory.start()
}

Benefit: Separate concerns with dedicated task queues


🚨 Common Mistakes

❌ Register After Start

// ❌ Wrong - Workers already started!
workerFactory.start()
worker.registerWorkflowImplementationTypes(...)

// βœ… Right - Register then start
worker.registerWorkflowImplementationTypes(...)
workerFactory.start()

More Common Mistakes

❌ Hardcoding Configuration

// ❌ Wrong - No flexibility
WorkflowServiceStubs.newServiceStubs("prod-temporal:7233")

// βœ… Right - Configurable
@Value("\${temporal.server.url}")
lateinit var serverUrl: String

Always use externalized configuration!


🧰 Troubleshooting Cheatsheet

Common Issues:

  • ❗ Connection refused β†’ Is Temporal running locally?
  • ❗ Bean creation failed β†’ Missing annotations or misconfigured @Bean
  • ❗ Worker not running β†’ Did you call start() after registration?

πŸ“‹ Quick Fix:

Check logs and use structured logging for quick diagnosis.


πŸ’‘ Key Takeaways

What You've Learned:

  • βœ… How to integrate Temporal with Spring Boot
  • βœ… Configuration patterns for different environments
  • βœ… Worker lifecycle management
  • βœ… Task queue concepts
  • βœ… Common pitfalls to avoid

πŸš€ What's Next?

You've laid the groundwork!

Next up in Lesson 3:

  • Spin up Temporal locally
  • Build your first end-to-end workflow
  • See everything working together

Ready to run Temporal locally? Let's go! πŸŽ‰

results matching ""

    No results matching ""