Skip to content

Commit 1f20482

Browse files
Spring-Kotlin Blog code samples (#1) (#277)
* initial commit of spring-into-kafka-cc. * Adding logging * Repo documentation, intro and setup details. * Update spring-into-kafka-cc/README.adoc - IDE reference * Update spring-into-kafka-cc/README.adoc - spacing * Removing tfplan from git. * Fixing doc, per code review. --------- Co-authored-by: Dave Troiano <[email protected]>
1 parent 8adcc18 commit 1f20482

30 files changed

+1496
-0
lines changed

spring-into-kafka-cc/.gitignore

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
HELP.md
2+
.gradle
3+
build/
4+
!gradle/wrapper/gradle-wrapper.jar
5+
!**/src/main/**/build/
6+
!**/src/test/**/build/
7+
8+
### STS ###
9+
.apt_generated
10+
.classpath
11+
.factorypath
12+
.project
13+
.settings
14+
.springBeans
15+
.sts4-cache
16+
bin/
17+
!**/src/main/**/bin/
18+
!**/src/test/**/bin/
19+
20+
### IntelliJ IDEA ###
21+
.idea
22+
*.iws
23+
*.iml
24+
*.ipr
25+
out/
26+
!**/src/main/**/out/
27+
!**/src/test/**/out/
28+
29+
### NetBeans ###
30+
/nbproject/private/
31+
/nbbuild/
32+
/dist/
33+
/nbdist/
34+
/.nb-gradle/
35+
36+
### VS Code ###
37+
.vscode/
38+
39+
### Kotlin ###
40+
.kotlin
41+
42+
### Ignore injection of enviroment vars from terraform commands. ###
43+
env.sh
44+
.env
45+
46+
### generated docx files for confluent blog ###
47+
*.docx

spring-into-kafka-cc/README.adoc

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
= Spring Into Kafka with Kotlin and Confluent Cloud
2+
Sandon Jacobs <sjacobs@confluent.io>
3+
3.0, July 29, 2022: AsciiDoc article template
4+
:toc:
5+
:icons: font
6+
:url-quickref: https://docs.asciidoctor.org/asciidoc/latest/syntax-quick-reference/
7+
8+
This demo will exercise various ways to integrate Spring Boot applications with Apache Kafka(R), specifically Confluent Cloud.
9+
10+
== The Spring and Kafka Friendship
11+
12+
The Spring for Apache Kafka (spring-kafka) project applies core Spring concepts to the development of Kafka-based solutions. It provides a "template" as a high-level abstraction for sending messages. It also provides support for Message-driven POJOs with `@KafkaListener` annotations and a "listener container". These libraries promote the use of dependency injection and declarative programming.
13+
14+
== Required Tools
15+
16+
.For ths demo, you'll need a few things:
17+
* JDK 21 (I strongly suggest https://sdkman.io/[sdkman].)
18+
* https://www.terraform.io/[Terraform]
19+
* https://confluent.cloud[Confluent Cloud] account
20+
* https://docs.confluent.io/confluent-cli/current/overview.html[Confluent CLI]
21+
* https://jqlang.github.io/jq/[jq]
22+
* IDE of choice. For Kotlin, that is likely https://www.jetbrains.com/idea/[IntelliJ IDEA] (Community Edition will suffice).
23+
24+
== Confluent Cloud
25+
26+
Once you have registered for a Confluent Cloud account, we'll be ready create the infrastructure for this demo. Here we'll use the Confluent Terraform Provider to provision assets in Confluent Cloud.
27+
28+
From your terminal, change into the `terraform` subdirectory and run the following commands:
29+
30+
```shell
31+
terraform init
32+
terraform plan -out "tfplan"
33+
terraform apply "tfplan"
34+
```
35+
36+
You can validate your Confluent Cloud assets are provisioned via the Confluent Cloud Console or with Confluent CLI. Should you encounter an issue at of the steps above, please validate your Confluent CLI setup (specifically credentials) and `PATH` variables.
37+
38+
The code in this demo relies on the assets you just provisioned, and the configuration of this Spring Boot application needs the credentials and endpoints we just provisioned. Let's export those to a properties file in the `USER_HOME` directory to later usage:
39+
40+
```shell
41+
terraform output -json | jq -r 'to_entries[] | .key + "=" + (.value.value | tostring)' | while read -r line ; do echo "$line"; done > . ~/tools/spring-into-cc.properties
42+
43+
cat ~/tools/spring-into-cc.properties
44+
45+
CC_BROKER=<REDACTED>.confluent.cloud:9092
46+
CC_BROKER_URL=https://<REDACTED>.confluent.cloud:443
47+
CC_ENV_DISPLAY_NAME=spring-into-cc
48+
CC_ENV_ID=<REDACTED>
49+
CC_KAFKA_CLUSTER_ID=<REDACTED>
50+
CC_SCHEMA_REGISTRY_ID=<REDACTED>
51+
CC_SCHEMA_REGISTRY_URL=https://<REDACTED>.confluent.cloud
52+
KAFKA_KEY_ID=<REDACTED>
53+
KAFKA_KEY_SECRET=<REDACTED>
54+
SCHEMA_REGISTRY_KEY_ID=<REDACTED>
55+
SCHEMA_REGISTRY_KEY_SECRET=<REDACTED>
56+
```
57+
58+
== Run the Demos
59+
60+
.Check out the documentation in each submodule for details on the code samples and how to execute them.
61+
* xref:./produce-consume/README.adoc[Producing and Consuming Events]
62+
63+
== Confluent Cloud Cleanup
64+
65+
Once you're done, you can destroy the Confluent Cloud environment using Terraform from the `terraform` directory as follows:
66+
67+
```shell
68+
terraform destroy -auto-approve
69+
```
70+
71+
== Useful Resources
72+
73+
.Here's a list resources you may find of use for this demo:
74+
* https://kotlinlang.org/[Kotlin Language]
75+
* https://spring.io/projects/spring-kafka[Spring-Kafka project]
76+
* https://spring.io/projects/spring-boot[Spring Boot]
77+
* https://gradle.org/[Gradle]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
plugins {
2+
id("org.springframework.boot") version "3.3.1" apply false
3+
id("io.spring.dependency-management") version "1.1.5" apply false
4+
id("org.graalvm.buildtools.native") version "0.10.2" apply false
5+
kotlin("jvm") version "1.9.24" apply false
6+
kotlin("plugin.spring") version "1.9.24" apply false
7+
id("com.bakdata.avro") version "1.0.0" apply false
8+
kotlin("plugin.serialization") version "2.0.0" apply false
9+
}
10+
11+
group = "io.confluent.devrel"
12+
version = "0.0.1-SNAPSHOT"
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
plugins {
2+
kotlin("jvm")
3+
kotlin("plugin.spring")
4+
id("com.bakdata.avro")
5+
kotlin("plugin.serialization")
6+
}
7+
8+
java {
9+
toolchain {
10+
languageVersion = JavaLanguageVersion.of(21)
11+
}
12+
}
13+
14+
repositories {
15+
mavenCentral()
16+
maven("https://packages.confluent.io/maven")
17+
}
18+
19+
sourceSets {
20+
main {
21+
kotlin.srcDirs("src/main/kotlin", "build/generated-main-avro-java")
22+
}
23+
}
24+
25+
val fakerVersion = "2.0.0-rc.3"
26+
27+
dependencies {
28+
// implementation("org.slf4j:slf4j-api:2.0.11")
29+
// implementation("org.slf4j:slf4j-simple:2.0.11")
30+
// implementation("ch.qos.logback:logback-core:1.4.14")
31+
32+
implementation(platform("io.github.serpro69:kotlin-faker-bom:$fakerVersion"))
33+
implementation("io.github.serpro69:kotlin-faker")
34+
implementation("io.github.serpro69:kotlin-faker-books")
35+
implementation("io.github.serpro69:kotlin-faker-tech")
36+
37+
implementation("org.jetbrains.kotlinx:kotlinx-cli:0.3.6")
38+
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0-RC.2")
39+
40+
implementation("org.apache.kafka:kafka-clients:3.7.0")
41+
implementation("io.confluent:kafka-avro-serializer:7.6.0")
42+
implementation("io.confluent:kafka-schema-rules:7.6.0")
43+
44+
testImplementation("org.jetbrains.kotlin:kotlin-test")
45+
}
46+
47+
kotlin {
48+
compilerOptions {
49+
freeCompilerArgs.addAll("-Xjsr305=strict")
50+
}
51+
}
52+
53+
tasks.withType<Test> {
54+
useJUnitPlatform()
55+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"type": "record",
3+
"namespace": "io.confluent.devrel.spring.model",
4+
"name": "Address",
5+
"fields": [
6+
{
7+
"name": "address1",
8+
"type": "string",
9+
"doc": "street address line 1"
10+
},
11+
{
12+
"name": "address2",
13+
"type": ["null", "string"],
14+
"default": null,
15+
"doc": "street address line 2, optional"
16+
},
17+
{
18+
"name": "city",
19+
"type": "string"
20+
},
21+
{
22+
"name": "state",
23+
"type": "string"
24+
},
25+
{
26+
"name": "postalCode",
27+
"type": "string"
28+
}
29+
]
30+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"type": "record",
3+
"namespace": "io.confluent.devrel.spring.command",
4+
"name": "CustomerCommand",
5+
"fields": [
6+
{
7+
"name": "action",
8+
"type": {
9+
"type": "enum",
10+
"namespace": "io.confluent.devrel.spring.command",
11+
"name": "CustomerAction",
12+
"symbols": [
13+
"ADD", "UPDATE", "DELETE", "UNKNOWN"
14+
],
15+
"default": "UNKNOWN"
16+
},
17+
"doc": "What action to apply to the given user."
18+
},
19+
{
20+
"name": "customer",
21+
"type": "io.confluent.devrel.spring.model.Customer",
22+
"doc": "Customer to operate on."
23+
}
24+
]
25+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"type": "record",
3+
"namespace": "io.confluent.devrel.spring.model",
4+
"name": "Customer",
5+
"fields": [
6+
{
7+
"name": "id",
8+
"type": ["null","string"],
9+
"default": null,
10+
"doc": "identifier for Customer"
11+
},
12+
{
13+
"name": "email",
14+
"type": "string",
15+
"doc": "email address"
16+
},
17+
{
18+
"name": "firstName",
19+
"type": "string",
20+
"doc": "first name"
21+
},
22+
{
23+
"name": "lastName",
24+
"type": "string",
25+
"doc": "last name"
26+
},
27+
{
28+
"name": "dob",
29+
"type": "string",
30+
"doc": "date of birth"
31+
},
32+
{
33+
"name": "mailingAddress",
34+
"type": "io.confluent.devrel.spring.model.Address",
35+
"doc": "mailing address"
36+
}
37+
]
38+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package io.confluent.devrel.spring.kfaker
2+
3+
import io.confluent.devrel.spring.model.Address
4+
import io.confluent.devrel.spring.model.Customer
5+
import io.github.serpro69.kfaker.faker
6+
import java.time.format.DateTimeFormatter
7+
8+
class BaseKFaker {
9+
10+
companion object {
11+
val kFaker = faker { }
12+
val techFaker = io.github.serpro69.kfaker.tech.faker { }
13+
14+
val dateTimeFormatter = DateTimeFormatter.ofPattern("MM/dd/yyyy")
15+
}
16+
17+
fun address(): Address {
18+
val addressFaker = kFaker.address
19+
20+
return Address.newBuilder()
21+
.setAddress1(addressFaker.streetAddress())
22+
.setAddress2(addressFaker.streetAddress())
23+
.setCity(addressFaker.city())
24+
.setState(addressFaker.state())
25+
.setPostalCode(addressFaker.postcode())
26+
.build()
27+
}
28+
29+
fun customer(): Customer {
30+
31+
val firstNameFaker = kFaker.name.firstName()
32+
val lastNameFaker = kFaker.name.lastName()
33+
34+
val email = kFaker.internet.email("${firstNameFaker}.${lastNameFaker}")
35+
val age = (18..75).random().toLong()
36+
37+
return Customer.newBuilder()
38+
.setId(kFaker.random.nextUUID())
39+
.setFirstName(firstNameFaker)
40+
.setLastName(lastNameFaker)
41+
.setEmail(email)
42+
.setMailingAddress(address())
43+
.setDob(dateTimeFormatter.format(kFaker.person.birthDate(age)))
44+
.build()
45+
}
46+
}
42.4 KB
Binary file not shown.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
distributionBase=GRADLE_USER_HOME
2+
distributionPath=wrapper/dists
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
4+
networkTimeout=10000
5+
validateDistributionUrl=true
6+
zipStoreBase=GRADLE_USER_HOME
7+
zipStorePath=wrapper/dists

0 commit comments

Comments
 (0)