External REST APIs
Overview
The Genesis HTTP Client is designed to simplify integration with external REST services in the Genesis Platform. It provides an intuitive DSL for making HTTP requests, so that you can integrate Request Server and Event Handler with external applications easily and without the need to pull their data into your Genesis application.
The key features and benefits are:
- Seamless Integration. Designed to work seamlessly with Genesis Request Servers and Event Handlers.
- Flexible Usage. Supports multiple approaches, including direct client usage, annotation-driven approach, and OpenAPI code generation.
- Comprehensive HTTP Support. Full support for GET, POST, PUT, DELETE.
- Customizable. Easily configure headers, query parameters, path parameters, and request bodies.
- Error Handling. Built-in support for retries and error callbacks.
- Paginated Support. Pagination support for Request Servers.
Ensure your Genesis Platform is updated to at least version 8.1 to use the Genesis HTTP Client.
Basic usage
Here's a quick example of a GET request using the Genesis HTTP Client within a Request Server:
import global.genesis.httpclient.GenesisHttpClient
requestReply<Unit, HelloWorld>("HELLO_WORLD") {
val client = GenesisHttpClient()
replySingle {
client.get<HelloWorld> {
url = "https://my-external-api/hello"
query("name", "John")
}.data
}
}
This example demonstrates how to make a GET request to an external REST API within a Genesis Request Server, showcasing the client's seamless integration with Genesis components. In the following sections, we'll explore the core concepts, different usage approaches, and advanced features of the Genesis HTTP Client.
Example configuration
This section demonstrates practical examples of using the Genesis HTTP Client in common Genesis capabilities Request Server and Event Handler and common use cases.
Example 1: Request Server with external API call
This example shows how to use the Genesis HTTP Client in a Request Server to fetch data from an external API, ensuring errors are handled:
import global.genesis.httpclient.GenesisHttpClient
import global.genesis.httpclient.response.UnexpectedResponseException
import global.genesis.message.core.HttpStatusCode
requestReplies {
requestReply<TradeRequest, TradeDetails>("FETCH_TRADE_DETAILS") {
val client = GenesisHttpClient()
replySingle { request: TradeRequest ->
try {
client.get<TradeDetails> {
url = "https://my-external-api/trades/${request.tradeId}"
}
} catch (e: UnexpectedResponseException) {
when (e.statusCode) {
HttpStatusCode.NotFound -> throw Exception("Trade not found")
else -> throw Exception("Error fetching trade: ${e.message}")
}
}
}
}
}
Example 2: Event Handler with external POST
This example demonstrates how to use the Genesis HTTP Client in an Event Handler to send a post to an external service as part of a TradeInsert event
import global.genesis.httpclient.GenesisHttpClient
eventHandler {
data class TradeNotification(
val tradeId: Long,
val status: String,
val timestamp: Long
)
eventHandler<TradeInsert> {
val client = GenesisHttpClient()
onCommit { event ->
...
val notification = TradeNotification(
tradeId = event.tradeId,
status = event.status,
timestamp = event.timestamp
)
try {
client.post<Unit>(
path = "https://my-external-api/trade-notification",
request = notification,
builder = {
header("Authorization ", "Bearer 123")
}
)
} catch (e: Exception) {
nack("Failed to send notification", e)
}
...
ack()
}
}
}
Example 3: Request Server with OpenAPI client
This example demonstrates how to use the OpenAPI-generated client service in a Request Server to get an account by id:
val tradeApiService = TradeApiService("https://my-external-api")
tradeApiService.registerApiToken("Authorization", "Basic ABC123=")
requestReplies {
requestReply<Int, Account>("GET_ACCOUNT_BY_ID") {
replySingle {
accountsApi.getAccountById(it.toLong())
}
}
}
Example 4 - Request Server with OpenAPI client inside an Event Handler
This example demonstrates how to use the OpenAPI-generated client service in an Event Handler to update a trade in an external system via an external call.
val tradeApiService = TradeApiService("https://my-external-api")
eventHandler {
eventHandler<TradeUpdateEvent> {
onCommit { event ->
val updatedTrade = NewTrade(
symbol = event.symbol,
quantity = event.quantity,
price = event.price
)
try {
tradeApiService.updateTrade(event.tradeId, updatedTrade)
ack()
} catch (e: Exception) {
logger.error("Failed to update trade in external system: ${e.message}", e)
}
}
}
}
Example 5 - Paginated request
This example demonstrates how to integrate a Request Server which a pagination supporting OpenAPI-generated REST API in an Event Handler using a paginated requestReply
:
val accountsApi = AccountControllerApi("https://my-external-api")
accountsApi.registerApiToken("Authorization", "Basic ABC123=")
requestReplies {
requestReply<FindAllUsingGETRequest, AccountWithRecordId>("ACCOUNTS_API") {
maxRetries = 5
replyList { request ->
try {
val response = accountsApi.findAllUsingGET(request)
val accounts = response.accounts.map { account ->
AccountWithRecordId(
account.accountNumber,
account.balance,
account.brokerId,
account.customerId,
account.owner
)
}
accounts
} catch (e: Exception) {
LOG.warn("Error sending request for accounts to external API", e)
emptyList()
}
}
}
}
Configuration options
GenesisHttpClient
The GenesisHttpClient
is the main class you interact with. It provides methods for making HTTP requests.
import global.genesis.httpclient.GenesisHttpClient
val client = GenesisHttpClient()
You can optionally configure a Ktor HttpClient object if needed. An example using Apache5 is provided below:
import global.genesis.httpclient.GenesisHttpClient
import io.ktor.client.HttpClient
import io.ktor.client.engine.apache5.Apache5
val apacheClient = HttpClient(Apache5) {
engine {
followRedirects = true
socketTimeout = 10_000
connectTimeout = 10_000
connectionRequestTimeout = 20_000
}
}
val client = GenesisHttpClient(apacheClient)
You can follow the documented examples on the ktor documentation site as needed, for example configuring SSL if required in your setup.
Request methods
The client supports all standard HTTP methods:
- GET:
client.get<T>()
- POST:
client.post<T>()
- PUT:
client.put<T>()
- DELETE:
client.delete<T>()
Each method above returns a TypedHttpResponse<T>
object found in global.genesis.httpclient.response.TypedHttpResponse
, where T
is the expected response body type.
Configuring requests
You can configure requests using a DSL within a lambda function. Common configuration options include:
url
: the endpoint URLheader
: HTTP headersquery
: query parameters
Example:
import global.genesis.httpclient.GenesisHttpClient
val httpClient = GenesisHttpClient()
...
eventHandler<Account>("API_ACCOUNT_INSERT") {
onCommit {
val data = httpClient.get<String> {
url = "https://my-external-api/accounts"
header("Authorization", "Basic ABCDEFG")
query("accountId", event.details.accountId)
}.data
...
ack()
}
}
...
It is important to highlight that if a payload needs to be provided as part of the HTTP request body (very common for POST
requests), then it must be provided as a request
parameter with a target path
as shown below:
import global.genesis.httpclient.GenesisHttpClient
val client = GenesisHttpClient()
...
eventHandler<Trade>("API_TRADE_INSERT") {
onCommit { event ->
...
client.post<String>(
path = "https://my-external-api/trades",
request = event.details
) {
header("Authorization", "Basic ABCDEFG")
query("tradeId", event.details.tradeId)
}.data
...
ack()
}
}
OpenAPI code generation
This approach uses code generated from an OpenAPI/Swagger specification, providing the simplest and most type-safe way to make API calls.
To use this approach, you need to generate the code from the OpenAPI specification file using the genesis-openapi-codegen
tool. The generated code will include API interfaces and data classes that map to the API endpoints and request/response bodies.
You must also include the following set-up in your module's build.gradle.kts
file:
plugins {
id("global.genesis.openapi") version "8.2.0" // Genesis Version
}
tasks {
jar {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
processResources {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
genesisOpenApi {
packageName = "global.genesis.api.trades"
specification = project.layout.projectDirectory.file("src/main/resources/trades.json")
}
}
In the example above, we are generating the code from the trades.json
, which is an open-api specification file. The generated code will be placed in the global.genesis.api.trades
package.
You can generate the code either by running the genesis-openapi-codegen
task or by building the project. You can then use the generated code to make API calls, for example:
val tradeApi = TradeServiceApi("https://my-external-api")
tradeApi.registerApiToken("Authorization", "Basic YWRtaW46YWRtaW4=")
import global.genesis.message.core.HttpStatusCode
val accountsApi = AccountsControllerApi("https://my-external-api")
accountsApi.registerRetryCallback(HttpStatusCode.Unauthorized) {
registerApiToken("Authorization", "Basic YWRtaW46YWRtaW4=")
}
The example below can be used inside a Genesis Request Server or Event Handler, for example:
eventHandler<Account>("API_ACCOUNT_EH_INSERT") {
onCommit {
val createdAccount = accountsApi.createAccount(it.details)
ack(listOf(mapOf("ACCOUNT_NUMBER" to createdAccount.accountNumber)))
}
}
Response handling
The TypedHttpResponse<T>
object found in global.genesis.httpclient.response.TypedHttpResponse
contains:
- statusCode: the HTTP status code
- headers: response headers
- data: the response body, deserialized to type T
Example:
import global.genesis.httpclient.GenesisHttpClient
val client = GenesisHttpClient()
...
eventHandler<Trade>("API_TRADE_INSERT") {
onCommit { event ->
...
client.post<String>(
path = "https://my-external-api/trades",
request = event.details
) {
header("Authorization", "Basic ABCDEFG")
query("tradeId", event.details.tradeId)
}.data
...
ack()
}
}
Client API
There is no specific Client API for the Genesis HTTP Client as it is a client itself. Links are provided below for the common services which use the Genesis HTTP Client:
Runtime configuration
The Genesis HTTP Client was introduced in version 8.1 of the Genesis Platform. It is compatible with all Genesis applications running on version 8.1 or higher.
The Genesis HTTP Client is available to use in Genesis applications running platform version 8.1+.
To use it, add the following dependency to your project's server/<your-app>/build.gradle.kts
file:
dependencies {
...
implementation("global.genesis:genesis-http-client")
}
If you would like to configure a different HTTP client engine, additional Ktor dependencies can be added like this:
val ktor_version = "3.2.2" // use the Genesis platform ktor version for best compatibility
dependencies {
...
implementation("io.ktor:ktor-client-apache5:$ktor_version")
}