The Concept of Idempotence
How important is Idempotence?
Implementing Idempotency
Example in Kotlin with Spring
Running the project
- Clone the repository
git clone https://github.com/daanrsantiago/idempotency-example- Navigate to the project directory:
cd idempotency-example- Build the project using gradle
./gradlew build- Run docker compose
docker compose up- Run the application
java -jar build/libs/idempotency-example-0.0.1-SNAPSHOT.jarImplementing the Publication and User Resources
Publication.kt
package dev.danielsantiago.idempotency.model
import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType.AUTO
import jakarta.persistence.Id
import java.time.LocalDateTime
@Entity
data class Publication(
@Id
@GeneratedValue(strategy= AUTO)
var id: Long? = null,
var title: String,
var content: String,
val creationDate: LocalDateTime = LocalDateTime.now()
)
User.kt
package dev.danielsantiago.idempotency.model
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType.AUTO
import jakarta.persistence.Id
import jakarta.persistence.Table
@Entity
@Table(name = "users")
data class User(
@Id
@GeneratedValue(strategy = AUTO)
var id: Long? = null,
val name: String,
val age: Int,
@Column(unique = true)
val cpf: String
)
Publication.kt
package dev.danielsantiago.idempotency.controller
import ...
@RestController
@RequestMapping("/publication")
class PublicationController(
val publicationRepository: PublicationRepository,
val template: RedisTemplate<String, String>
) {
val PREFIX = "publication:idempotency:"
...
@PostMapping()
fun publishPublication(@RequestBody body: PublicationRequest): ResponseEntity<Publication> {
val publishedPublication = publicationRepository.save(
Publication(
title = body.title,
content = body.content
)
)
return ResponseEntity.status(CREATED).body(publishedPublication)
}
@PostMapping("/idempotent")
fun eventualIdempotency(@RequestBody body: PublicationRequest): ResponseEntity<Publication> {
if(template.opsForValue().setIfAbsent(PREFIX + body.hashCode(), "1", Duration.ofMinutes(1))!!) {
val publishedPublication = publicationRepository.save(
Publication(
title = body.title,
content = body.content
)
)
return ResponseEntity.status(CREATED).body(publishedPublication)
} else {
return ResponseEntity.status(UNPROCESSABLE_ENTITY).build()
}
}
...
}
