Skip to content

Commit

Permalink
Split code into different microservices based on features: webmvc, jp…
Browse files Browse the repository at this point in the history
…a, kafka.
  • Loading branch information
gnagy committed May 7, 2024
1 parent e84560a commit 141144c
Show file tree
Hide file tree
Showing 29 changed files with 267 additions and 64 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Spring example project

This is a simple example project that demonstrates how to use Spring Boot with Kotlin.
This repo contains several Spring Boot example projects to demonstrate various features available in
the Spring ecosystem in a purposefully opinionated way to help showcase best practices. Each example
microservice is a separate subproject in the `spring-apps` directory, which is the root project of
the gradle build.

These projects can also be used as components to test drive other things, such as CICD pipelines /
Github workflows, monitoring, etc.

## Docs

Expand Down
2 changes: 2 additions & 0 deletions spring-apps/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
rootProject.name = "spring-apps"

include("spring-webmvc")
include("spring-jpa")
include("spring-cloud-stream-kafka")
3 changes: 0 additions & 3 deletions spring-apps/spring-cloud-stream-kafka/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,18 @@ dependencies {
implementation(platform(libs.spring.cloud.dependencies))

implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.cloud:spring-cloud-starter-stream-kafka")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation(libs.logstash.logback.encoder)
runtimeOnly("io.micrometer:micrometer-registry-prometheus")
runtimeOnly("org.postgresql:postgresql")
developmentOnly("org.springframework.boot:spring-boot-devtools")

testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.boot:spring-boot-testcontainers")
testImplementation("org.testcontainers:junit-jupiter")
testImplementation("org.testcontainers:postgresql")
testImplementation("org.springframework.kafka:spring-kafka-test")
testImplementation("org.awaitility:awaitility")
testImplementation("org.testcontainers:kafka")
Expand Down
10 changes: 0 additions & 10 deletions spring-apps/spring-cloud-stream-kafka/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,4 @@
services:
postgres:
image: postgres:14-alpine
ports:
- '5432:5432'
environment:
POSTGRES_DB: spring-example
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_HOST_AUTH_METHOD: trust

kafdrop:
image: obsidiandynamics/kafdrop
ports:
Expand Down
10 changes: 10 additions & 0 deletions spring-apps/spring-cloud-stream-kafka/requests.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
### new transaction
POST http://127.0.0.1:8080/transactions HTTP/1.1
Content-Type: application/json

{
"priority": 3,
"accountNumber": "ACC-123456",
"amount": 100,
"description": "Test transaction"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.vacuumlabs.example

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class SpringCloudStreamKafkaApplication

fun main(args: Array<String>) {
runApplication<SpringCloudStreamKafkaApplication>(*args)
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package com.vacuumlabs.example.kafka

import com.vacuumlabs.example.db.MessageEntity
import com.vacuumlabs.example.db.MessageRepository
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.function.Consumer

@Configuration
class KafkaConfiguration {
val logger: Logger = LoggerFactory.getLogger(javaClass)

@Bean("message-saver")
fun messageSaver(messageRepository: MessageRepository) =
Consumer<TransactionDto> { message ->
if (message.accountNumber != "ACC-123456") {
throw java.lang.IllegalArgumentException("Account doesn't exist: ${message.accountNumber}")
fun messageSaver(transactionRepository: TransactionRepository) =
Consumer<TransactionDto> { transaction ->
if (transaction.accountNumber != "ACC-123456") {
throw java.lang.IllegalArgumentException("Account doesn't exist: ${transaction.accountNumber}")
}
messageRepository.save(MessageEntity(id = null, message = message.description ?: ""))
transactionRepository.save(transaction)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.vacuumlabs.example.kafka

import org.springframework.stereotype.Service

@Service
class TransactionRepository() {
private val transactions = mutableListOf<TransactionDto>()

fun save(transaction: TransactionDto) {
transactions.add(transaction)
}

fun findAll(): List<TransactionDto> {
return transactions
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.vacuumlabs.example.kafka

import com.vacuumlabs.example.db.MessageRepository
import org.springframework.cloud.stream.function.StreamBridge
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
Expand All @@ -9,7 +8,6 @@ import org.springframework.web.bind.annotation.RestController
@RestController
class TransactionsController(
private val streamBridge: StreamBridge,
private val messageRepository: MessageRepository,
) {
@PostMapping("/transactions")
fun transactionSender(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,3 @@ spring:
message-saver-in-0:
destination: test-topic
group: message-saver
datasource:
url: jdbc:postgresql://localhost:5432/spring-example
username: postgres
password: postgres

spring.jpa.hibernate.ddl-auto: create
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,14 @@ package com.vacuumlabs.example

import org.springframework.boot.fromApplication
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.testcontainers.service.connection.ServiceConnection
import org.springframework.boot.with
import org.springframework.context.annotation.Bean
import org.springframework.test.context.DynamicPropertyRegistry
import org.testcontainers.containers.KafkaContainer
import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.utility.DockerImageName

@TestConfiguration(proxyBeanMethods = false)
class ContainerConfiguration {
@Bean
@ServiceConnection
fun postgresContainer(): PostgreSQLContainer<*> {
return PostgreSQLContainer(DockerImageName.parse("postgres:14-alpine"))
}

@Bean
fun kafkaContainer(properties: DynamicPropertyRegistry): KafkaContainer {
val kafka = KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.1.2"))
Expand All @@ -27,5 +19,5 @@ class ContainerConfiguration {
}

fun main(args: Array<String>) {
fromApplication<ExampleApplication>().with(ContainerConfiguration::class).run(*args)
fromApplication<SpringCloudStreamKafkaApplication>().with(ContainerConfiguration::class).run(*args)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package com.vacuumlabs.example

import com.fasterxml.jackson.databind.ObjectMapper
import com.vacuumlabs.example.db.MessageEntity
import com.vacuumlabs.example.db.MessageRepository
import com.vacuumlabs.example.kafka.TransactionDto
import com.vacuumlabs.example.kafka.TransactionRepository
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.clients.consumer.KafkaConsumer
import org.apache.kafka.common.TopicPartition
Expand Down Expand Up @@ -33,20 +32,21 @@ class KafkaIntegrationTests
constructor(
val mockMvc: MockMvc,
val objectMapper: ObjectMapper,
val messageRepository: MessageRepository,
val transactionRepository: TransactionRepository,
val kafkaContainer: KafkaContainer,
) {
@Test
@DirtiesContext
fun `new transaction`() {
val transactionDto = TransactionDto(1, "ACC-123456", BigDecimal(1000), "Test transaction")
postNewTransaction(
TransactionDto(1, "ACC-123456", BigDecimal(1000), "Test transaction"),
transactionDto,
).andExpect { status { isOk() } }

Awaitility.await().pollDelay(Duration.ofMillis(100)).atMost(Duration.ofSeconds(5)).until {
messageRepository.findAll().toList().isNotEmpty()
transactionRepository.findAll().toList().isNotEmpty()
}
assertThat(messageRepository.findAll()).isEqualTo(listOf(MessageEntity(1, "Test transaction")))
assertThat(transactionRepository.findAll()).isEqualTo(listOf(transactionDto))
}

@Test
Expand All @@ -59,7 +59,7 @@ class KafkaIntegrationTests
val record = awaitRecord("error.test-topic.message-saver")
val exceptionMessage = record?.headers()?.lastHeader("x-exception-message")?.value()?.decodeToString()
assertThat(exceptionMessage).endsWith("Account doesn't exist: ACC-654321")
assertThat(messageRepository.findAll()).isEmpty()
assertThat(transactionRepository.findAll()).isEmpty()
}

private fun postNewTransaction(transactionDto: TransactionDto): ResultActionsDsl {
Expand Down
50 changes: 50 additions & 0 deletions spring-apps/spring-jpa/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.spring)
alias(libs.plugins.kotlin.jpa)
alias(libs.plugins.spring.boot)
alias(libs.plugins.spring.dependency.management)
alias(libs.plugins.ktlint)
alias(libs.plugins.benmanes.versions)
}

group = "com.vacuumlabs.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17

repositories {
mavenCentral()
}

dependencies {
implementation(platform(libs.spring.cloud.dependencies))

implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation(libs.logstash.logback.encoder)
runtimeOnly("io.micrometer:micrometer-registry-prometheus")
runtimeOnly("org.postgresql:postgresql")
developmentOnly("org.springframework.boot:spring-boot-devtools")

testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.boot:spring-boot-testcontainers")
testImplementation("org.testcontainers:junit-jupiter")
testImplementation("org.testcontainers:postgresql")
}

tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "17"
}
}

tasks.withType<Test> {
useJUnitPlatform()
}
10 changes: 10 additions & 0 deletions spring-apps/spring-jpa/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:
postgres:
image: postgres:14-alpine
ports:
- '5432:5432'
environment:
POSTGRES_DB: spring-example
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_HOST_AUTH_METHOD: trust
2 changes: 2 additions & 0 deletions spring-apps/spring-jpa/requests.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
### get messages
GET http://127.0.0.1:8080/messages HTTP/1.1
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class ExampleApplication
class SpringJpaApplication

fun main(args: Array<String>) {
runApplication<ExampleApplication>(*args)
runApplication<SpringJpaApplication>(*args)
}
22 changes: 22 additions & 0 deletions spring-apps/spring-jpa/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
probes:
enabled: true

spring:
profiles:
active: # json-logging
application:
name: spring-example
datasource:
url: jdbc:postgresql://localhost:5432/spring-example
username: postgres
password: postgres

spring.jpa.hibernate.ddl-auto: create
14 changes: 14 additions & 0 deletions spring-apps/spring-jpa/src/main/resources/logback-spring.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<configuration>
<springProfile name="default">
<include resource="org/springframework/boot/logging/logback/base.xml"/>
</springProfile>

<springProfile name="json-logging">
<appender name="jsonConsoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
<root level="INFO">
<appender-ref ref="jsonConsoleAppender"/>
</root>
</springProfile>
</configuration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.vacuumlabs.example

import org.springframework.boot.fromApplication
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.testcontainers.service.connection.ServiceConnection
import org.springframework.boot.with
import org.springframework.context.annotation.Bean
import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.utility.DockerImageName

@TestConfiguration(proxyBeanMethods = false)
class ContainerConfiguration {
@Bean
@ServiceConnection
fun postgresContainer(): PostgreSQLContainer<*> {
return PostgreSQLContainer(DockerImageName.parse("postgres:14-alpine"))
}
}

fun main(args: Array<String>) {
fromApplication<SpringJpaApplication>().with(ContainerConfiguration::class).run(*args)
}

0 comments on commit 141144c

Please sign in to comment.