This project is a Kotlin and Java-based application that uses Gradle for build automation. It includes a railway-oriented programming approach to handle operations and errors gracefully.
- Railway-oriented programming for error handling
- User creation service with validation
- Gradle build automation
- Logging with KotlinLogging
Railway-Oriented Programming (ROP) is a functional programming pattern that helps manage operations that can succeed or fail. It is particularly useful for handling complex workflows with multiple steps, where each step can potentially fail. The pattern is inspired by the idea of a railway track, where the process can either stay on the "success" track or switch to the "failure" track if an error occurs.
The RailwayHandler
class encapsulates the result of an operation, which can either be a success (with a value) or a failure (with an error). It provides methods to chain operations (flatMap
and map
) and handle errors gracefully.
The RailwayHandler
class has companion object methods success
and failure
to create instances representing successful and failed operations, respectively.
The flatMap
method is used to chain operations. If the current operation is successful, it applies the provided function to the value and returns a new RailwayHandler
with the result. If the current operation has failed, it skips the function and returns the current failure.
In the main
function, several operations (like multiplyByTwo
, subtractFive
, validateNumber
, and convertToString
) are chained together using flatMap
. If any operation fails, the chain short-circuits, and the error is propagated.
The UserService
class demonstrates how to use ROP for a more complex workflow, such as creating a user. It validates the input, maps it to an entity, saves it to a repository, and maps it back to a DTO, handling errors at each step.
Here is a simplified example of chaining operations using RailwayHandler
:
val multiplyByTwo = { number: Int -> RailwayHandler.success(number * 2) }
val subtractFive = { number: Int -> RailwayHandler.success(number - 5) }
val validateNumber = { number: Int ->
when {
number > 100 -> RailwayHandler.failure(IllegalArgumentException("Number must be less than 100"))
number < 0 -> RailwayHandler.failure(IllegalArgumentException("Number must be greater than 0"))
else -> RailwayHandler.success(number)
}
}
val convertToString = { number: Int -> RailwayHandler.success(number.toString()) }
val result: RailwayHandler<String>? = RailwayHandler
.success(15)
.flatMap(validateNumber)
?.flatMap(subtractFive)
?.flatMap(multiplyByTwo)
?.flatMap(convertToString)
if (result?.isSuccess == true) {
println("Operation succeeded with result: ${result.value}")
} else {
println("Operation failed: ${result?.error?.message}")
}