• Stars
    star
    111
  • Rank 314,510 (Top 7 %)
  • Language
    Kotlin
  • License
    Apache License 2.0
  • Created over 5 years ago
  • Updated over 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Flexible and type-safe inline HTTP client for Android and Kotlin

Ninjato

Bintray version Kotlin version badge codecov

Flexible and type-safe inline HTTP client for Android and Kotlin

What is Ninjato?

Ninjato is a library that lets you write simple yet powerful remote HTTP calls, whether it's RESTful services or any other. But at the same time the library gives you the complete control of the flow.

The library is written in Kotlin and tries to rely on reflection as low as possible. Most of the functionality is implemented with the help of inlining and reified types inference during compile time.

Why not Retrofit?

Retrofit is a great library, and people at Square do amazing and very important stuff. But for us at Agoda, after a long time with Retrofit we started to feel limited with the way how the library gives us opportunities to customize, intercept and implement more flexible behavior.

How to use?

Basic syntax

In a nutshell, Ninjato gives a possibility to execute any type of HTTP call through Api class. As such, you can have several approaches to define and use your remote services:

Extend the Api class directly:

class YourApi(client: HttpClient, config: Api.() -> Unit) : Api(client, config) {
    override val baseUrl = "https://yourApi.com"
    
    fun search(query: String): SearchResult = get {
        endpointUrl = "/search"
        parameters { "query" to query }
    }
}

Hide implementation behind the interface:

interface YourApi {
    fun updateArticle(article: Article)
}

class YourApiImpl(client: HttpClient, config: Api.() -> Unit) : Api(client, config), YourApi {
    override val baseUrl = "https://yourApi.com"
    
    fun updateArticle(article: Article) = post {
        endpointUrl = "/updateArticle"
        body = article
    }
}

Or implement your endpoints right in the interface:

interface YourApi {
    val api: Api
    
    fun putListing(listing: Listing) = api.put {
        endpointUrl = "/putListing"
        body = listing
    }
}

There are a lot more ways how you can organize your remote services' code, but we're not going to cover it here.

Main features

As you already understood from the examples below, Ninjato can infer the type of body and what is expected to be returned from the request. But there are a lot more features. All of them will be explained in the sections below with code examples.

DSL cascade: HttpClient, Api and Request

Ninjato is structured in a way that allows it's users to aggregate properties and/or behavior of their remote calls through the DSL cascade. This means that all main entities of library: HttpClient, Api and Request have the same configuration interface where you can configure your headers, parameters, interceptors, etc.

During the actual call configuration, all of these configurations will be aggregated from bottom to top, or replaced, if only one value is allowed.

Request body and return type inference

Thanks to Kotlin capabilities, Ninjato infers the type of body being set (if allowed) and expected return type.

Default types of body property include:

  • Body
  • String
  • ByteArray

Default types of return value include:

  • Unit
  • Response
  • Body
  • Stirng
  • ByteArray

For all custom types library has support of BodyConverter.

IMPORTANT NOTE: there is an issue with the Kotlin compiler and inlining of delegates with reified types is not working as expected. That means that current syntax of passing your body is inconsistent. Right now if you will pass an instance of generic class to a body property, type arguments will be lost:

fun foo(generic: Generic<String>) = post {
    body = generic // It will capture only class of the generic via generic.javaClass
}

That puts the responsibility of inferring the type arguments on your serializing library. Gson is doing this fine. If you want library to capture actual type and forward it to your BodyConverter.Factory, please consider using the newly introduced extension function:

fun foo(generic: Generic<Stirng>) = post {
    body = convert(generic)
}

BodyConverter

BodyConverter is a simple interface with a convert function from one to another. Library uses the provided BodyConverter.Factory instances to get the converter for a specific type. You can provide supplied or your own factory to any level of the DSL cascade:

val client = NinjatoOkHttpClient(okHttpClient) {
    converterFactories += GsonConverterFactory(Gson())
}

val api = YourApi(client) {
    converterFactories += MoshiConverterFactory()
}

val result: SearchResult = api.get {
    endpointUrl = "/get"
    converterFactories += JacksonConverterFactory()
}

List of body converters provided via extension artifacts can be found in Setup section.

Interceptors

Interceptors are instances that are executed prior (RequestInterceptor) the request is served by the HTTP client and after (ResponseInterceptor) the response was acquired. Interceptors have the ability to modify given instance of Request/Response or even provide completely another instances.

Interceptors can be added to any level of the DSL cascade. The following code can be used also in Api classes and in any call configuration:

val client = NinjatoOkHttpClient(okHttpClient) {
    interceptors += MyRequestInterceptor()
    interceptors += MyResponseInterceptor()
    
    interceptors {
        request { r ->
            Log.d(r)
            r
        }
        
        response { r ->
            Log.d(r)
            r
        }
    }
}

RetryPolicy

Simply put, RetryPolicy is a class that can allow you to decide, whether or not given request should try again and be executed. You get the Request and Throwable as input and should return one of the Retry sealed class children: DoNotRetry, WithoutDelay or WithDelay.

Retry policy can be added to any level of the DSL cascade. The following code can be used also in Api classes and in any call configuration:

val client = NinjatoOkHttpClient(okHttpClient) {
    retryPolicy = MyRetryPolicy()
    
    retryPolicy { request, throwable ->
        if (request.retries > 3) Retry.DoNotRetry else Retry.WithoutDelay
    }
}

FallbackPolicy

In case your RetryPolicy allowed a request to be retried, FallbackPolicy can help you with modifying you request before a retry attempt. For example, change the baseUrl of your request.

Fallback policy can be added to any level of the DSL cascade. The following code can be used also in Api classes and in any call configuration:

val client = NinjatoOkHttpClient(okHttpClient) {
    fallbackPolicy = MyFallbackPolicy()
    
    fallbackPolicy { request, throwable -> 
        request.also { it.baseUrl = "https://anotherServer.com" }
    }
}

Headers and query parameters

Headers and url query parameters are also part of the DSL cascade.

Headers and query parameters can be added to any level of the DSL cascade. The following code can be used also in HttpClient and Api classes:

val result: SearchResult = get {
    endpointUrl = "/search"
    headers += "A" to "B"
    
    headers {
        "B" to "C"
        
        cookie {
            "C" to "D"
            expires = 3600
            isSecure = true      
        }
    }
    
    parameters {
        "query" to query
    }
}

Request.Factory

To add possibility to extend Request entity and enrich it with use-case specific properties and/or logic, Ninjato has a factory that will return instances of Request which can be manipulated further in interceptors:

class YourRequestFactory : Request.Factory() {
    override fun create() = YourRequest()
}

class YourRequestInterceptor : RequestInterceptor() {
    fun intercept(request: Request): Request {
        if (request is YourRequest) {
            request.headers["request_id"] = request.id
        }
    }
}

val client = NinjatoOkHttpClient(okHttpClient, YourRequestFactory()) {
    interceptors += YourRequestInterceptor()
}

Response.Factory

To add possibility to extend Resposne entity and enrich it with use-case specific properties and/or logic, Ninjato has a factory that will return instances of Response which can be manipulated further in interceptors:

class YourResponseFactory : Response.Factory() {
    override fun create() = YourResponse()
}

// usage example
class YourResponseInterceptor {
    fun intercept(response: Response): Response {
        if (response is YourResponse) {
            response.token = response.headers["auth_token"]
        }
    }
}

val client = NinjatoOkHttpClient(okHttpClient, YourRequestFactory(), YourResponseFactory()) {
    interceptors += YourRequestInterceptor()
    interceptors += YourResponseInterceptor()
}

Wrappers

In case you prefer to use some sealed class type wrapper, or RxJava, for example, Ninjato has you covered as well. There are extension artifacts available that are providing extension wrapping functions for your Api classes. Here are few examples:

interface YourApi {
    val api: Api
    
    fun search(query: String): Call<SearchResult> = api.call {
        get {
            endpointUrl = "/serach"
            params { "query" to query }
        }
    }
    
    fun updateArticle(article: Article): Completable = api.completable {
        post {
            endpointUrl = "/articles"
            body = article
        }
    }
    
    fun putListing(listing: Listing): Flowable<Response> = api.flowable {
        put {
            endpointUrl = "/listings"
            body = listing
        }
    }
}

List of available extension artifacts can be found in Setup section.

More

For any additional information please refer to library's documentation.

Setup

Maven

<!-- Core library !-->
<dependency>
  <groupId>com.agoda.ninjato</groupId>
  <artifactId>ninjato-core</artifactId>
  <version>LATEST_VERSION</version>
  <type>pom</type>
</dependency>

<!-- OkHttp 3 client !-->
<dependency>
  <groupId>com.agoda.ninjato</groupId>
  <artifactId>client-okhttp</artifactId>
  <version>LATEST_VERSION</version>
  <type>pom</type>
</dependency>

<!-- Gson body converter !-->
<dependency>
  <groupId>com.agoda.ninjato</groupId>
  <artifactId>converter-gson</artifactId>
  <version>LATEST_VERSION</version>
  <type>pom</type>
</dependency>

<!-- Call wrapper !-->
<dependency>
  <groupId>com.agoda.ninjato</groupId>
  <artifactId>extension-call</artifactId>
  <version>LATEST_VERSION</version>
  <type>pom</type>
</dependency>

<!-- RxJava wrappers !-->
<dependency>
  <groupId>com.agoda.ninjato</groupId>
  <artifactId>extension-rxjava</artifactId>
  <version>LATEST_VERSION</version>
  <type>pom</type>
</dependency>

<!-- RxJava2 wrappers !-->
<dependency>
  <groupId>com.agoda.ninjato</groupId>
  <artifactId>extension-rxjava2</artifactId>
  <version>LATEST_VERSION</version>
  <type>pom</type>
</dependency>

or Gradle:

repositories {
    jcenter()
}

dependencies {
    // Core library
    implementation 'com.agoda.ninjato:ninjato-core:LATEST_VERSION'

    // OkHttp 3 client
    implementation 'com.agoda.ninjato:client-okhttp:LATEST_VERSION'

    // Gson body converter
    implementation 'com.agoda.ninjato:converter-gson:LATEST_VERSION'

    // Call wrapper
    implementation 'com.agoda.ninjato:extension-call:LATEST_VERSION'

    // RxJava wrappers
    implementation 'com.agoda.ninjato:extension-rxjava:LATEST_VERSION'

    // RxJava 2 wrappers
    implementation 'com.agoda.ninjato:extension-rxjava2:LATEST_VERSION'
}

Contribution Policy

Ninjato is an open source project, and depends on its users to improve it. We are more than happy to find you interested in taking the project forward.

Kindly refer to the Contribution Guidelines for detailed information.

Code of Conduct

Please refer to Code of Conduct document.

License

Ninjato is available under the Apache License, Version 2.0.

Thanks to

More Repositories

1

Kakao

This repo is no longer supported. Please visit a https://github.com/KakaoCup/Kakao
Kotlin
1,110
star
2

docker-emulator-android

Dockerized android emulator
Shell
247
star
3

android-farm

Android devices farm with USB and emulated devices support
Smarty
165
star
4

boots

Lightweight bootstrap library for your Kotlin, Java and Android apps
Kotlin
91
star
5

samsahai

Dependencies verification system with Kubernetes Operator
Go
80
star
6

Agoda.IoC

C# IoC extension library, used at Agoda for Registration of classes into IoC container based on Attributes.
C#
39
star
7

react-handyman

👨‍🔧 Enhance your React.js app with i18n, string formatting, ab tests and other handy packages!
TypeScript
37
star
8

opentelemetry-logs-go

Opentelemetry Logs Go library
Go
35
star
9

net-loadbalancing

.NET Standard library for simple retry handling and load balancing
C#
25
star
10

adb-butler

adb server for OpenSTF deployment
Shell
22
star
11

AgodaAnalyzers

A set of opinionated Roslyn analyzers for C#
C#
21
star
12

standards-c-sharp

Agoda's standards and practices for C# code
20
star
13

kafka-jdbc-connector

Simple way to copy data from relational databases into kafka.
Scala
20
star
14

opentelemetry-go

Extensions to OpenTelemetry (Golang)
Go
18
star
15

spark-hpopt

Bayesian hyperparamter tuning for Spark MLLib
Jupyter Notebook
10
star
16

eslint-config-agoda

Agoda's ESLint configuration
JavaScript
8
star
17

agoda-design-toolkit

Sketch Plugin - Quickly generate real content for your design workflow (Internal use)
JavaScript
4
star
18

dotnet-build-metrics

Measure Compilation time of Dev local C# projects
C#
4
star
19

tslint-rules

A set of TSLint rules used on some Agoda projects.
TypeScript
3
star
20

Rebus.Kafka

C#
2
star
21

teldrassil

Visualize dependency graphs in your Intelli J projects
Kotlin
2
star
22

otelzap

Zap logger with OpenTelemetry support
Go
2
star
23

kafka-jdbc-connector-samples

Kafka JDBC Connector sample projects
Scala
2
star
24

android-ci

Android CI scripts
Dockerfile
1
star
25

samsahai-example

Samsahai Integration Configuration
Makefile
1
star
26

cicd

JavaScript
1
star
27

ga-npm-wildcard-check

PowerShell
1
star
28

Shouldly.FromAssert

C#
1
star
29

StructuredMapper

C#
1
star
30

KafkaFlow.ApplicationInsights

C#
1
star