Ktor: Crafting a Stock Portfolio Endpoint – Part 2.5
Before we delve into Part 3, which focuses on unit testing, it's essential to address one key preparation step to improve the testability and reusability of the code: dependency injection. This post directly follows Part 2 of a series of posts that aim to build a single endpoint with Ktor by wrapping the AlphaVantage Quote Endpoint. I recommend at least skimming through the previous posts if you haven't read them yet, as it will provide useful context. Having said that, if you're already caught up with the series, let's dive into dependency injection.
Why Dependency Injection?
In simple terms, dependency injection is a process in which objects or functions are injected (or passed) into another object or function instead of being created internally. As the concept of dependency injection has been extensively covered in other articles and videos, this section will specifically focus on its application in our server codebase. Upon revisiting the codebase, you may notice the following line of code.
Remember how we used the Bridge design pattern for testability in Part 2? However, the AlphaVantageApi
interface hasn't been utilized until now. Instead of creating the AlphaVantageApiImpl
internally inside configureRouting
, we can pass the interface as a parameter.
Imagine there are other implementations of the AlphaVantageApi
(e.g. AlphaVantageComplexApiImpl
). The new configureRouting
delegates the creation of AlphaVantageApi
and should work just fine regardless of the specific implementations of AlphaVantageApi
. Thus, the api
is injected into the configureRouting
instead of created internally.
Another advantage of dependency injection is testability. To test the configureRouting
, the api
should be mocked and return fake data. Otherwise, the value of the real endpoint changes based on the real stock data, which makes it extremely hard to maintain the tests.
Koin Setup
In a project of this scale, the full potential of using a dependency injection framework might not be immediately apparent. For this tutorial, we'll explore Koin, a tool I've not used previously for the sake of learning together. Another great reason to choose Koin is its great documentation when it comes to setting Koin up in a Ktor project.
Similar to adding any other libraries, we need to add the dependencies first.
To specify the koinVersion
mentioned above, we need to adjust gradle.properties
file.
Following Koin documentation for Ktor, the next step is to install the Koin plugin with the following code.
Remember to call this new function in the Application.kt
itself.
There is still one problem (compilation error) because applicationModule
isn't defined yet. For that reason, let's learn more about modules to create them.
Modules
Once again, the Koin documentation offers comprehensive information about modules in its Definitions section. In short, a Koin module is a logical space to organize the definitions and configurations for the dependency injection. It requires minimal code to define a module. Given the simplicity of our server, consolidating all dependency injection logic into a single module is the most reasonable approach. Let's create an empty applicationModule
in a new file to fix the previous problem.
After importing applicationModule
in Koin.kt, our code is ready to run smoothly again with the proper Koin setup.
Injection
Up to this point, our focus has been on preparatory steps. Whenever we need to inject an object, the following steps are the only necessary ones.
First, we need to determine which object to inject. In our case, it is the AlphaVantageApiImpl
as discussed in the first section. With the following setup, Koin will provide AlphaVantageApiImpl
whenever AlphaVantageApi
is required.
The keyword single
indicates that the provided AlphaVantageApi
is a Singleton
. In practical terms, this ensures that every time AlphaVantageApi
is requested, the same instance of AlphaVantageApiImpl
is used. In order to provide AlphaVantageApi
, simply add this code to Application.kt
.
Remember to change the configureRouting
function to receive the api
parameter as discussed in the first section for the code to work.
Conclusion
While the core functionality of the code remains unchanged, the design pattern has been significantly enhanced for better testability and maintainability. I hope this post has provided you with a clearer understanding of dependency injection in a Ktor project. Don’t forget to share your thoughts or questions below, and make sure to subscribe to our newsletter to not miss out on Part 3, which will cover unit testing!
Comments ()