Featured image of post Microsoft Kiota: The Bespoke Premium REST API Client - Part 2

Microsoft Kiota: The Bespoke Premium REST API Client - Part 2

In the second, final part of my series, we’ll dive into building a REST API client using Microsoft Kiota. Leveraging the OpenAPI specification we made in the previous post, we’ll instruct Kiota to generate a decent client tailored to our needs. Additionally, we’ll explore creating an ad-hoc, customized client using the Visual Studio Code extension.

🎒 Resources

Previously, we’ve created a very minimalistic REST API using Spring Boot and the springdoc-openapi library. So, we have a working API with a decent OpenAPI specification. Now is the time to see Kiota in action:

  1. We will generate a Java client using the Maven plugin during the build process and run some end-to-end tests - belt and braces.
  2. Also, we will see how to create a Python client in an ad-hoc manner using the Visual Studio Code extension.

Why Kiota

Before diving into the real action, let me explain why I am going to choose Kiota over other client generators in the future.

Dependency on Service Providers Decisions

As a consumer of REST APIs, I often find myself depending on the service provider’s decisions. Consider this scenario: You need to interact with a large-scale service, but your business requirements focus on a specific subset of its functionality (e.g., read-only access via GET requests). In such cases, having an SDK client that supports all endpoints and operations becomes unnecessary overhead.

Managing Dependencies

Another challenge arises from dependencies. If a client SDK relies on a specific library (e.g., for JSON serialization), it can lead to uncomfortable situations. Have you ever found yourself excluding a transitive dependency in your pom.xml file because it conflicts with another library? To avoid such situations, Kiota strives to keep external dependencies to a minimum. Even better, it allows you to choose the libraries for serialization, deserialization, and HTTP requests.

Empowering Consumers with Custom SDKs

Finally, by providing the OpenAPI specification, you empower consumers to tailor the SDK to their specific needs. They know best how to use the service effectively.

☝️ Wrapping up, Kiota plays well with the OpenAPI specification, allows you to manage dependencies, and empowers you, the consumer to create a custom SDK, based on your needs.
Let’s put consumers back in the driver’s seat, like in the good old WSDL & SOAP era! 💪

Generate a Java Client Using Kiota & Maven

Alright - let’s dive in and create a Java client for our REST API, which we developed in the previous post.

Previously, we focused solely on the api module. However, if you explored my repository, you may have noticed two more modules: client and sdk-kiota. We’ll begin by examining the sdk-kiota module, which serves as the foundation for the SDK client and is a dependency for the client module.

Build an SDK Client Library

Add the Kiota Maven plugin to the module’s pom.xml file. As previously discussed, this Maven plugin is not part of the official Kiota repository. Instead, you can find it in the kiota-java-extra repository. The <plugins> section should look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<plugins>
    <plugin>
        <artifactId>kiota-maven-plugin</artifactId>
        <groupId>io.kiota</groupId>
        <version>${kiota.plugin.version}</version>
        <executions>
            <execution>
                <goals>
                    <goal>generate</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <file>${project.parent.basedir}/api/target/openapi.json</file>
            <namespace>com.github.tcsizmadia.sandbox.kiota.sdkclient</namespace>
            <kiotaVersion>${kiota.cli.version}</kiotaVersion>
            <cleanOutput>true</cleanOutput>
            <clearCache>true</clearCache>
            <!-- The Maven plugin references for io.kiota.serialization.json.*
                    So I've declared the (de)serializers explicitly -->
            <deserializers>
                <deserializer>com.microsoft.kiota.serialization.JsonParseNodeFactory</deserializer>
                <deserializer>com.microsoft.kiota.serialization.TextParseNodeFactory</deserializer>
                <deserializer>com.microsoft.kiota.serialization.FormParseNodeFactory</deserializer>
            </deserializers>
            <serializers>
                <serializer>com.microsoft.kiota.serialization.JsonSerializationWriterFactory</serializer>
                <serializer>com.microsoft.kiota.serialization.TextSerializationWriterFactory</serializer>
                <serializer>com.microsoft.kiota.serialization.FormSerializationWriterFactory</serializer>
                <serializer>com.microsoft.kiota.serialization.MultipartSerializationWriterFactory</serializer>
            </serializers>
        </configuration>
    </plugin>
  • The kiota.plugin.version and kiota.cli.version properties are defined in my parent pom.xml file.
  • The file property points to the OpenAPI specification file generated by the springdoc-openapi library.
  • The namespace property defines the package name for the generated classes.
  • The cleanOutput and clearCache properties are self-explanatory.

Please pay attention to the <deserializers> and <serializers> sections. As I mentioned earlier, Kiota enables you to select your preferred libraries for serialization, deserialization, and HTTP requests. Although I’ve opted for the default options in this instance, this is where you can specify alternative libraries of your choice.

Given that we generate the client code as part of the build process, it’s essential to include the SDK client code in the project’s source files. Without this step, the generated classes would be absent from the jar file. To ensure this, insert the following code snippet into your pom.xml file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>build-helper-maven-plugin</artifactId>
    <version>3.2.0</version>
    <executions>
        <execution>
            <id>add-source</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>add-source</goal>
            </goals>
            <configuration>
                <sources>
                    <source>${project.build.directory}/generated-sources/kiota</source>
                </sources>
            </configuration>
        </execution>
    </executions>
</plugin>

Nice, now we are ready to generate the client code. Let’s build the sdk-kiota module:

1
mvn clean install

If everything goes well, you should something like this in the console:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[info] Going to execute the command: /Users/tamas/git/kiota-sandbox/sdk-kiota/target/kiota/1.14.0/kiota 
generate --openapi /Users/tamas/git/kiota-sandbox/api/target/openapi.json 
--output /Users/tamas/git/kiota-sandbox/sdk-kiota/target/generated-sources/kiota/com/github/tcsizmadia/sandbox/kiota/sdkclient 
--language Java --class-name ApiClient --namespace-name com.github.tcsizmadia.sandbox.kiota.sdkclient 
--serializer com.microsoft.kiota.serialization.JsonSerializationWriterFactory 
--serializer com.microsoft.kiota.serialization.TextSerializationWriterFactory 
--serializer com.microsoft.kiota.serialization.FormSerializationWriterFactory 
--serializer com.microsoft.kiota.serialization.MultipartSerializationWriterFactory --deserializer com.microsoft.kiota.serialization.JsonParseNodeFactory 
--deserializer com.microsoft.kiota.serialization.TextParseNodeFactory 
--deserializer com.microsoft.kiota.serialization.FormParseNodeFactory 
--clean-output true --clear-cache true --log-level Warning

Generation completed successfully

Consume the REST API using the SDK Client

Now that we have the SDK client as a JAR library, let’s create a client module that uses it. The client module is a vanilla Java application that interacts with the REST API - using the SDK client library from the sdk-kiota module.

Add the sdk-kiota module as a dependency in the client module’s pom.xml file:

1
2
3
4
5
6
<dependency>
    <groupId>com.github.tcsizmadia</groupId>
    <artifactId>sdk-kiota</artifactId>
    <version>${project.parent.version}</version>
    <scope>compile</scope>
</dependency>

Next, create the client code. Without further ado, here is an example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Main {
    public static void main(String[] args) {
        final var authProvider = new AnonymousAuthenticationProvider();
        final var requestAdapter = new OkHttpRequestAdapter(authProvider);
        final var client = new ApiClient(requestAdapter);

        Objects.requireNonNull(client.persons().get()).forEach((p) -> {
            System.out.println("\n" + p.getName() + " (" + p.getOccupation().toString().toLowerCase() + ")");
            System.out.println("--------------------------");

            if (null != p.getId()) {
                Objects.requireNonNull(client.quotes().author().byAuthorId(p.getId()).get()).forEach((q) -> {
                    System.out.println(q.getContent());
                });
            }
        });

        try {
            System.out.println("\nCreating a new person...");
            var person = new Person();
            person.setName("John Doe");
            person.setOccupation(PersonOccupation.ATHLETE);
            // Remember, to create or update a person, we need to authenticate ourselves.
            client.persons().post(person);
        } catch (ApiException e) {
            // This is expected, because we are using an anonymous authentication provider.
            System.err.println("Failed to create a new person: " + e.getMessage());
        }
    }
}

While the code includes some embarrassing stuff (e.g., System.out.println calls), it shows the real value of Kiota. As you can see, the client code is strongly typed, boasts an elegant, fluent interface, that is straightforward to understand. It’s as if I had written it myself! 😅

I hope the code itself pretty much explains what it does. But in a nutshell, it fetches all the persons, with their occupations, and prints out the quotes associated with those people. Finally, it tries to create a new person, which will fail due to the lack of decent authentication - my apologies for the lousy error handling - at least I’ve tried 🤷. Anyways, the client comes with a decent ApiException class, which contains the response status code and the response headers as well.

Testing the Client in Practice

Are you excited to see the above in action? I am as well. Let’s run some end-to-end tests to ensure everything works as expected. In a real-world scenario, I would go with Testcontainers to spin up a Docker container with the REST API. However, for the sake of simplicity, I will spin up the Spring Boot application locally and test the client against it manually.

First, start the Spring Boot application:

1
2
cd api
mvn spring-boot:run

☝️ Make sure the port number 8080 is available. If not, you can change it in the application.properties file to a different port (e.g. server.port=8081).

Next, run the client:

1
2
cd client
mvn exec:java -Dexec.mainClass="com.github.tcsizmadia.sandbox.kiota.client.Main"

You should see the quotes fetched from the REST API:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Winston Churchill (politician)
--------------------------
Success is not final, failure is not fatal: It is the courage to continue that counts.
The price of greatness is responsibility.
Success consists of going from failure to failure without loss of enthusiasm.

Woody Allen (actor)
--------------------------
I'm not afraid of death; I just don't want to be there when it happens.
Eighty percent of success is showing up.
If you're not failing every now and again, it's a sign you're not doing anything very innovative.

Albert Einstein (scientist)
--------------------------
The only source of knowledge is experience.
Imagination is more important than knowledge.
The true sign of intelligence is not knowledge but imagination.

Margaret Thatcher (politician)
--------------------------
You may have to fight a battle more than once to win it.
If you want something said, ask a man; if you want something done, ask a woman.
I always cheer up immensely if an attack is particularly wounding because I think, well, if they attack one personally, it means they have not a single political argument left.

Meryl Streep (actor)
--------------------------
The great gift of human beings is that we have the power of empathy.
Acting is not about being someone different. It's finding the similarity in what is apparently different, then finding myself in there.
Integrate what you believe in every single area of your life. Take your heart to work and ask the most and best of everybody else, too.

Taylor Swift (musician)
--------------------------
People haven't always been there for me but music always has.
Life isn't how to survive the storm, it's about how to dance in the rain.
Unique and different is the new generation of beautiful. You don't have to be like everyone else.

…and the expected error message:

1
2
Creating a new person...
Failed to create a new person: the server returned an unexpected status code and no error class is registered for this code 401

That’s it! 👏 We have successfully created a Java client using Kiota and the Maven plugin - only a few lines of code, and a decent OpenAPI specification were needed!

Please don’t shut down the Spring Boot application yet, as we will need it for the next section.

Create a Python Client Using the Visual Studio Code Extension

Another convenient way to create a client is by using the Kiota extension for Visual Studio Code. This extension is available in the Visual Studio Code Marketplace.

You can either install the extension locally, or use my DevContainer configuration, which includes the extension already.

☝️ If you are using an Aarch64-based machine (e.g., Apple Silicon), you should expect some performance degradation, as the Kiota extension is not available for ARM64 architecture.

Generate a Python Client

Click on the Kiota icon in the Visual Studio Code sidebar and click on the Open API description button. Paste the following URL into the input field (make sure the Spring Boot application is running):

1
http://localhost:8080/v3/api-docs

Kiota Extension in Action

  1. Now you should see the endpoints in a tree view. Feel free to exclude the unnecessary endpoints - concentrate on the GET calls - and click on the Generate API client (with the “play” icon) button.

  2. Next, name your client (e.g., QuoteApiClient),

  3. and specify a namespace.

  4. Now you need to specify the output directory, like python/client.

  5. Finally, select Python - stable as the language, and you are done!

Here is the generated Python client’s directory structure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
├── kiota-lock.json
├── models
│   ├── person.py
│   ├── person_occupation.py
│   └── quote.py
├── persons
│   ├── item
│   │   └── persons_item_request_builder.py
│   └── persons_request_builder.py
├── python_api_client.py
└── quotes
    ├── author
    │   ├── author_request_builder.py
    │   └── item
    │       └── with_author_item_request_builder.py
    └── quotes_request_builder.py

Next time, you can use the kiota-lock.json file, so you can work on your client generation incrementally, even though your backend is offline.

Test the Python Client

☝️ You can check out the complete Python client code from the feature/python-client-sample branch in my sample repository.

Let’s do the preparation for the Python client. First, define the required packages in the requirements.txt file:

1
2
3
4
5
6
7
microsoft-kiota-abstractions==1.3.2
microsoft-kiota-http==1.3.1
microsoft-kiota-serialization-json==1.2.0
microsoft-kiota-authentication-azure==1.0.0
microsoft-kiota-serialization-text==1.0.0
microsoft-kiota-serialization-form==0.1.0
microsoft-kiota-serialization-multipart==0.1.0

Then, create a Python script to test the client:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from client.quotes_api_client import QuotesApiClient
from kiota_abstractions.authentication.anonymous_authentication_provider import (
    AnonymousAuthenticationProvider)
from kiota_http.httpx_request_adapter import HttpxRequestAdapter

async def main():
    auth_provider = AnonymousAuthenticationProvider()
    request_adapter = HttpxRequestAdapter(auth_provider)

    client = QuotesApiClient(request_adapter)

    persons = await client.persons.get()

    for person in persons:
        quotes = await client.quotes.author.by_author_id(person.id).get()
        for quote in quotes:
            print(f"{person.name}: {quote.content}") 

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

If you’re feeling a sense of déjà vu, rest assured, you’re not alone. Although the Python code’s syntax is different, the structure and concepts are the same as the Java code. That’s the power of Kiota: thanks to its idiomatic nature, it lets you build clients in various languages, keeping the core structure and ideas the same, and still follows the best practices of each language.

Conclusion

In this two-part series, we delved into the capabilities of Microsoft Kiota, a powerful tool for crafting custom REST API clients. Our journey began with the construction of a REST API using Spring Boot, with the help of the springdoc-openapi library. Progressing further, we used the Kiota Maven plugin to generate a Java client and conducted some minimal end-to-end testing - manually. Subsequently, we crafted a Python client with ease using the Visual Studio Code extension.

Is Kiota the right choice for your upcoming project? Despite being in the early phase of development, Kiota is full of potential. For those who consume REST APIs and desire control over their client SDK, this is a tool worth exploring. It grants the freedom to forge a bespoke SDK that aligns perfectly with your specific requirements, without the burden of a bloated SDK or the hassle of managing dependency conflicts. The icing on the cake is the exceptional Visual Studio Code extension, which streamlines the client creation process—especially crucial during those fast-paced development cycles where rapid iteration is key.

I trust you found this series useful! If you wish to share your thoughts or have any questions, feel free to drop a comment below. Until next time! 👋

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy