Refactoring to the Repository Pattern
Вставка
- Опубліковано 3 жов 2024
- It never ceases to amaze me how bad I am at predicting the subject of the next week’s video. This week I really did set out to benchmark Ktor (ktor.io/) and http4k ((http4k.org) for endpoints that perform IO, but it very quickly became clear that the data model from the example project wasn’t suitable. It uses Kotlin’s MutableList, but the operations on that interface are not suspend functions, and Ktor needs coroutines in order to not block on IO.
So instead of measuring I ended up refactoring, migrating from MutableList to a repository object that is much more amenable to implementation with a database, even if in the tests we still actually hold data in an ArrayList.
In this episode
00:00:44 Our current model is MutableList of Customer
00:02:09 REST is also a model
00:02:49 Fixing the response for an empty list
00:04:46 What is the problem with MutableList?
00:05:40 Start by introducing a typealias for MutableList of Customer
00:07:12 Now make the type alias an interface
00:08:31 IntelliJ import bug
00:10:44 We can't serialize our custom type, but we can sidestep
00:11:58 Implement findById
00:15:00 Implement addCustomer
00:16:08 Implement deleteById
00:17:09 Now make Customers not a List
00:17:21 Lean on the compiler to find issues
00:17:36 Distinguishing between production and test operations
00:20:36 Some final tidying and conveniences
00:22:47 Customers now lets us see the operations we are using
00:23:17 There is a problem with suspend though
00:23:58 that we'll punt into next week
The code for this video is on a GitHub fork github.com/dmc...
This video is in a playlist of Ktor episodes ( • Ktor ) and http4k ( • http4k )
I get lots of questions about the test progress bar. It was written by the inimitable @dmitrykandalov. To use it install his Liveplugin (plugins.jetbra...) and then this gist gist.github.co...
If you like this video, you’ll probably like my book Java to Kotlin, A Refactoring Guidebook (java-to-kotlin.dev). It's about far more than just the syntax differences between the languages - it shows how to upgrade your thinking to a more functional style.
Unfortunately I don't think there is a way to abstract over suspend and non suspend methods: once you go `suspend` then everything needs to `suspend`. I worked on a codebase some time ago that dealt with this and we just decided to always make interfaces suspend since once an implementation needs it then you're screwed. I guess this is the famous "function coloring" problem.
I think you’re right, and I’ve heard of other codebases with the same policy. Which is a real shame, because suspend functions are definitely harder to test and I assume less efficient to call.
I wonder if, if they had their time again, the language designers would have made suspend a context, with special compiler handling, but at least passing the hidden CoroutineContext that way.
Is there not a refactor to propagate suspend up the call hierarchy when you find you need it?
@@PairingWithDuncan I don't think passing the context would be enough since suspend functions are either compiled to a state machine or to continuations (I work mostly with C# where state machines are used, not sure about Kotlin).
In C#, once you find something `async` you either force it to be sync (losing the benefits of async in the first place), our you need to propagate the async all the way to `main`. In my experience the best thing is to just start with `async`/`suspend` from main and move on.
In languages like Scala or Haskell you can abstract this away since you have access to "generics of generics" (aka higher kinded types), allowing you to write interfaces where the methods/functions are parametrized over some "context" (ex. `fun foo(s: String) -> m Int`, where `m` can be a sync/async context)
Kotlin compiles suspend to a state machine too, that’s the special compiler handling that would have to come with the context. As I understand it, that means that every invocation of a suspend function from a suspend function requires the extra code, which means that we pay a performance and code size price.
Kotlin’s context receivers, (soon, irritatingly, to be context parameters) will allow us to pass the async context kind of implicitly, but given the state machine issue, that doesn’t really help to generalise the calling, as you say.
I agree Kotlin has no way to abstract over function 'coloring'. In my mind, this is a good argument for http4k over ktor. Ktor is more complicated than http4k but in most cases there's no practical benefit.