CLOVIS
01/07/2023, 4:18 PMsuspend fun getAge(user: UserId): Outcome<Int> = out {
// require/check-inspired methods to declare failure causes…
ensureAuthenticated(userService.current() == user) { "You cannot get the age of another user" }
// …which can be caught at a previous layer to transform into HTTP status codes
user.get().age
}
Progression reporting
Pedestal adds the ability to report progression events in any coroutine directly through the CoroutineContext:
suspend fun getAge(user: UserId) = out {
ensureAuthenticated(userService.current() == user) { … }
report(loading(0.25)) // 25%
return user.get()
.also { report(loading(0.8)) } // 80%
.age
}
The progress events can be added gradually after the API is already written without breaking changes, as functions too slow are discovered. It is easy for the caller to access reporting events, for example with Compose :
@Composable
fun Age(user: UserId) {
val progress by remember { mutableStateOf<Progression>(Progression.done()) }
var age by remember { mutableStateOf<Int?>(null) }
LaunchedEffect(user) {
callbackReporter({ progress = it }) { // extract loading events into the State instance
report(loading(0.0))
age = getAge(user)
report(done())
}
}
}
Extended example: https://opensavvy.gitlab.io/pedestal/documentation/state/index.html
Aggressive caching
By writing your API such that objects are always referred by ID, it is possible to aggressively cache all read requests, centralizing write requests. This way, it becomes easier to write applications that are more automatically more performant! For example, with Compose:
@Composable
fun Age(ref: UserRef) {
// Dereference the Ref through the cache
// You don't have to worry if another component in the same page has already made the query!
val user by remember(ref) { ref.request().collectAsState() }
user.onSuccess {
Text("Age: ${it.age}")
}
// The cache is aware of failures
user.onFailure {
Text("Could not get the data for the requested user: $it")
Button({ ref.expire() }) { // will automatically update all occurrences of this value in the entire app
Text("Retry")
}
}
// The cache is aware of loading events
user.onLoading {
Text("Age: loading ${it.percent}")
}
}
All of this is implemented on top of #flow, so it can be used with any UI framework, not just Compose.
Extended example: https://opensavvy.gitlab.io/pedestal/documentation/backbone/index.html
Multiplatform
All modules are multiplatform (currently JVM + JS). Very few platform-specific code exists, making it easy to port to new platforms (if another platform seems interesting, please contribute :)). This allows for example to have the same error management in all parts of the application, or to use the same cache algorithm to cache API requests client-side than you use on your backend to cache database queries.
Experimental cross-platform API declaration
I'm exploring the possibility of declaring an entire REST API in the common module, to be able to typesafely refer to it in the other modules, empowering IntelliJ to make refactors throughout all sides of the API automatically.
https://gitlab.com/opensavvy/pedestal