Skip to content

Commit 8a2e4c0

Browse files
author
mdwairi
committed
Merge branch 'main' into mdwairi/fix-template-bug
2 parents f02e108 + 43dc3f9 commit 8a2e4c0

File tree

13 files changed

+719
-17
lines changed

13 files changed

+719
-17
lines changed

.release-please-manifest.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
"expediagroup-sdk-core": "1.0.0",
3-
"expediagroup-sdk-graphql": "1.0.0",
4-
"expediagroup-sdk-rest": "1.0.0",
5-
"expediagroup-sdk-openapi-plugin": "1.0.0",
6-
"expediagroup-sdk-transport-okhttp": "1.0.0"
2+
"expediagroup-sdk-core": "0.0.10-alpha",
3+
"expediagroup-sdk-graphql": "0.0.8-alpha",
4+
"expediagroup-sdk-rest": "0.0.13-alpha",
5+
"expediagroup-sdk-openapi-plugin": "0.0.15-alpha",
6+
"expediagroup-sdk-transport-okhttp": "0.0.6-alpha"
77
}

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import java.time.Duration
44

55
plugins {
66
id 'java'
7-
id 'org.jetbrains.kotlin.jvm' version '2.1.10' apply false
7+
id 'org.jetbrains.kotlin.jvm' version '2.2.0' apply false
88
id "org.jlleitschuh.gradle.ktlint" version "12.1.2" apply false
99
id 'org.jetbrains.kotlinx.kover' version "0.9.1" apply false
1010
id 'io.github.gradle-nexus.publish-plugin' version '2.0.0'

expediagroup-sdk-core/README.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
[![Maven Central](https://img.shields.io/maven-central/v/com.expediagroup/expediagroup-sdk-core.svg)](https://search.maven.org/artifact/com.expediagroup/expediagroup-sdk-core)
2+
3+
# Expedia Group JVM SDK - Core Module
4+
The `expediagroup-sdk-core` module serves as a reusable component that abstracts common functionalities, such as HTTP request execution, authentication, logging, and execution pipeline processing. It empowers product SDKs to focus on business-specific logic while ensuring consistency, flexibility, and maintainability.
5+
6+
## Usage
7+
In most cases, you don’t need to add `expediagroup-sdk-core` directly, just include `expediagroup-sdk-rest` or `expediagroup-sdk-graphql` in your project, and the core module will be pulled in transitively.
8+
9+
<details>
10+
<summary><strong>Maven</strong></summary>
11+
12+
Add the `expediagroup-sdk-core` as a dependency in your `pom.xml`:
13+
14+
```xml
15+
<dependency>
16+
<groupId>com.expediagroup</groupId>
17+
<artifactId>expediagroup-sdk-core</artifactId>
18+
<version>{latest-version}</version>
19+
</dependency>
20+
```
21+
</details>
22+
23+
24+
<details>
25+
<summary><strong>Gradle</strong></summary>
26+
27+
Add the `expediagroup-sdk-core` as a dependency in your `build.gradle`:
28+
29+
```gradle
30+
implementation 'com.expediagroup:expediagroup-sdk-core:{latest-version}'
31+
```
32+
</details>
33+
34+
## Architecture & Components
35+
The SDK Core module serves as the foundational "shell" that abstracts the execution of requests and responses via an **injected** HTTP client. It acts as a toolkit, empowering product SDKs to deliver a polished and user-friendly experience by providing ready-to-use components such as request execution, logging, authentication, and exception handling. The SDK core streamlines the development of cohesive SDKs.
36+
37+
### Transport Package
38+
The `Transport` interface defines the abstraction layer for making HTTP requests within the SDK. This interface allows SDK users to integrate their preferred HTTP client or transport mechanism while adhering to a standardized contract. The SDK relies on this interface to execute requests and process responses, offering flexibility and compatibility across diverse environments and libraries. To achieve complete abstraction, the SDK core module introduces standardized HTTP models customized for SDK usage (in the http package), such as `Request` and `Response`, which all `Transport` implementations are required to use. These models ensure consistency in how the SDK interacts with external systems, regardless of the underlying HTTP client or transport mechanism.
39+
40+
> [!TIP]
41+
> Refer to SDK Transport Usage Guide for more information and examples
42+
43+
### HTTP Package
44+
As mentioned earlier, the core module doesn’t depend on any specific HTTP client. To make this possible, the SDK defines its own generic HTTP request and response models. At runtime, those SDK models are translated into the native types required by your chosen HTTP client (for example, converting to OkHttp’s `Request` and `Response` objects).
45+
46+
[http package](https://github.com/ExpediaGroup/expediagroup-java-sdk/tree/main/expediagroup-sdk-core/src/main/kotlin/com/expediagroup/sdk/core/http)
47+
48+
### Pipeline Package
49+
While supporting injectable HTTP clients provides significant flexibility, it is essential to ensure that all requests made by the SDK adhere to governance and observability standards. This includes logging, authentication, client identification, and other critical processes, regardless of the underlying HTTP client being used. To achieve this, the core module introduces an `ExecutionPipeline`, which every product SDK must implement to integrate with the core.
50+
51+
Each product SDK defines its own execution pipeline by composing "request pipeline **steps**" and "response pipeline **steps**". The core ensures that these steps are executed in the correct sequence: request steps are applied before the request is executed, and response steps are applied after the response is received.
52+
53+
The primary entry point for integrating a product SDK with the core is through the `AbstractRequestExecutor` for synchronous calls and the `AbstractAsyncRequestExecutor` for asynchronous calls. These abstract classes enforce a consistent integration pattern by requiring implementers (i.e., product SDKs) to define the necessary `ExecutionPipeline`. This guarantees that governance and observability processes are applied consistently across all product SDKs, irrespective of the HTTP client used.
54+
55+
[pipeline package](https://github.com/ExpediaGroup/expediagroup-java-sdk/tree/main/expediagroup-sdk-core/src/main/kotlin/com/expediagroup/sdk/core/pipeline)
56+
57+
### Authentication Package
58+
The core module includes a suite of prebuilt components for handling common authentication schemes - such as Basic Auth & OAuth - so you don’t have to reinvent the wheel. True to the core’s “pluggable pipeline” philosophy, it provides the building blocks for each authentication workflow but leaves it up to the product SDK to decide when to invoke them.
59+
60+
In practice, you simply add one of the supplied pipeline steps (for example, `BasicAuthStep` or `OAuthStep`) to your request execution pipeline. If your product SDK requires a custom authentication mechanism that isn’t yet provided, you can implement the `RequestPipelineStep` interface yourself, insert your new step into the pipeline, and encapsulate all of your custom auth logic there. This approach keeps authentication concerns isolated, consistent, and easy to extend.
61+
62+
[auth package](https://github.com/ExpediaGroup/expediagroup-java-sdk/tree/main/expediagroup-sdk-core/src/main/kotlin/com/expediagroup/sdk/core/auth)
63+
64+
### Logging Package
65+
All SDK modules rely on the `SLF4J` API for logging without shipping a concrete implementation, so you remain free to choose any `SLF4J` binding (such as Logback or Log4j2) at runtime. To turn on request and response logging, simply register the built-in `RequestLoggingStep` and `ResponseLoggingStep` in your execution pipeline; as long as you include a valid `SLF4J` binding on your classpath, those steps will automatically emit detailed logs for each outbound HTTP request and inbound HTTP response through your chosen logging framework.
66+
67+
_A product SDK shouldn't provide the SLF4J implementation itself. This should be up to the end-user of the product SDK._
68+
69+
Beyond basic request and response logging, the core module also lets you automatically redact any sensitive information - whether it lives in HTTP headers or in the body of a request or response - so that secrets never end up in your logs.
70+
71+
[logging package](https://github.com/ExpediaGroup/expediagroup-java-sdk/tree/main/expediagroup-sdk-core/src/main/kotlin/com/expediagroup/sdk/core/logging)
72+
73+
### Exception Package
74+
The SDK has a set of exception models each for a defined purpose, categorized based on the exception nature:
75+
- Client exception: Any exception that might be thrown or caused by the SDK itself (e.g. Configuration exception)
76+
- Service exception: Any exception that's caused by the remote server (e.g Authentication Exception).
77+
- All exceptions extend `ExpediaGroupException`.
78+
- All service exceptions extend `ExpediaGroupServiceException`.
79+
- All client exceptions extend `ExpediaGroupClientException`.
80+
81+
[exception packagae](https://github.com/ExpediaGroup/expediagroup-java-sdk/tree/main/expediagroup-sdk-core/src/main/kotlin/com/expediagroup/sdk/core/exception)
82+
83+
84+
## Metadata Loader
85+
The core module includes a utility for loading runtime metadata such as SDK name, JVM version, SDK version, locale, etc... from `sdk.properties` file. Each product SDK must bundle this file in its resources, and it’s typically generated automatically during the build process.
86+
87+
## Full Integration View - Diagram
88+
The diagram below illustrates the architecture and interaction between a product SDK and the SDK Core, highlighting the flow of operations and the role of various components in synchronous and asynchronous request execution.
89+
90+
![Untitled-4](https://github.com/user-attachments/assets/65174c90-1881-4651-bef0-6554f00dab86)
91+
92+
#### Operations & Models
93+
- Located at the far left, these represent the business-specific **operations** and models defined by the product SDK. These models will be typically **generated** by OpenAPI generator for REST SDKs, and from Apollo Kotlin for GraphQL SDKs.
94+
- These are entirely decoupled from the core module and are responsible for defining the domain-specific request and response structures.
95+
- The operations are converted into SDK requests, which are later executed via the core module.
96+
97+
#### Sync Client
98+
- Represents the high-level client responsible for handling synchronous requests.
99+
- Converts an operation into a valid SDK request and sends it for execution.
100+
- Should encapsulate one implementation of `AbstractRequestExecutor` where this client can access the `execute` method along with the execution pipeline.
101+
- After receiving the response, the sync client converts the SDK response back into the corresponding operation response.
102+
103+
#### Async Client
104+
- Represents the high-level client handling asynchronous requests.
105+
- Converts an operation into a valid SDK request and sends it for execution.
106+
- Should encapsulate one implementation of `AbstractAsyncRequestExecutor` where this client can access the `execute` method along with the execution pipeline.
107+
- Once the response is received (via a `CompletableFuture`), it is asynchronously converted into the corresponding operation response.
108+
109+
#### Core Module:
110+
- SDK HTTP Models: Standardized Request and Response objects for consistent communication.
111+
- Authentication Module: Handles various authentication mechanisms.
112+
- Logging Module: Enables request/response logging for observability.
113+
- Metadata Loader: Loads extra runtime information about the SDK such as artifact name, version, Java version, and host OS.
114+
115+
#### Execution Pipelines
116+
- Request Pipeline: Depicted with blue blocks, this pipeline processes the SDK request through a series of defined steps (e.g., adding headers, logging) before sending it to the transport layer.
117+
- Response Pipeline: Depicted with green blocks, this pipeline processes the SDK response through defined steps (e.g., logging) after it is received from the transport layer.
118+
119+
#### Transport Layer
120+
- Sync Transport: Processes synchronous requests and returns a blocking response.
121+
- Async Transport: Processes asynchronous requests and returns a `CompletableFuture<Response>`.
122+
- By default, both `Transport` and `AsyncTransport` use the same HTTP client instance.
123+
124+
125+
126+
127+
128+

expediagroup-sdk-core/build.gradle

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ dependencies {
1717
api 'com.squareup.okio:okio:3.15.0'
1818

1919
/* Serialization/Deserialization */
20-
compileOnly(platform('com.fasterxml.jackson:jackson-bom:2.19.1'))
20+
compileOnly(platform('com.fasterxml.jackson:jackson-bom:2.19.2'))
2121
compileOnly 'com.fasterxml.jackson.core:jackson-databind'
2222
compileOnly 'com.fasterxml.jackson.module:jackson-module-kotlin'
2323

@@ -30,14 +30,14 @@ dependencies {
3030
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
3131
testImplementation 'org.junit.jupiter:junit-jupiter-params'
3232

33-
testImplementation 'io.mockk:mockk:1.14.4'
33+
testImplementation 'io.mockk:mockk:1.14.5'
3434
testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'
3535

3636
testImplementation 'org.slf4j:slf4j-api:2.0.17'
3737

38-
testImplementation(platform('com.fasterxml.jackson:jackson-bom:2.19.1'))
38+
testImplementation(platform('com.fasterxml.jackson:jackson-bom:2.19.2'))
3939
testImplementation 'com.fasterxml.jackson.core:jackson-databind'
40-
testImplementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.19.1'
40+
testImplementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.19.2'
4141
}
4242

4343
test {

expediagroup-sdk-graphql/README.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
[![Maven Central](https://img.shields.io/maven-central/v/com.expediagroup/expediagroup-sdk-graphql.svg)](https://search.maven.org/artifact/com.expediagroup/expediagroup-sdk-graphql)
2+
3+
# Expedia Group JVM SDK - GraphQL Module
4+
Building an SDK for GraphQL APIs presents unique challenges, such as defining the schema, constructing queries and mutations, and generating models. To address these requirements, this module was built as an extension package that adds GraphQL API support using Apollo Kotlin.
5+
6+
Product SDKs that require GraphQL capabilities simply include this extension dependency, and Maven or Gradle will automatically pull in the core module transitively.
7+
8+
## Installation
9+
10+
_The `expediagroup-sdk-graphql` requires Java 8 or higher._
11+
12+
<details>
13+
<summary><strong>Maven</strong></summary>
14+
15+
Add the `expediagroup-sdk-graphql` as a dependency in your `pom.xml`:
16+
17+
```xml
18+
<dependency>
19+
<groupId>com.expediagroup</groupId>
20+
<artifactId>expediagroup-sdk-graphql</artifactId>
21+
<version>{latest-version}</version>
22+
</dependency>
23+
```
24+
</details>
25+
26+
27+
<details>
28+
<summary><strong>Gradle</strong></summary>
29+
30+
Add the `expediagroup-sdk-graphql` as a dependency in your `build.gradle`:
31+
32+
```gradle
33+
implementation 'com.expediagroup:expediagroup-sdk-graphql:{latest-version}'
34+
```
35+
</details>
36+
37+
## Architecture & Components
38+
The GraphQL module builds on the core abstractions to streamline GraphQL client setup and provides a compatibility layer that adapts Apollo Kotlin–generated operations to the core request/response pipeline with minimal configuration.
39+
40+
### Apollo Kotlin Integration
41+
It's important to highlight that the GraphQL module depends only on the Apollo Kotlin API and its code-generation plugin and **not** on Apollo’s HTTP engine. This enables the SDK to work seamlessly with Apollo-generated operation classes and models while delegating all HTTP requests to its own transport layer. Meanwhile, Apollo remains responsible for serializing queries and deserializing responses, ensuring type-safe handling of the GraphQL payloads.
42+
43+
### GraphQL Clients
44+
The GraphQL module provides a high-level abstract class, `GraphQLClient`, which serves as the integration point between your product SDK and the core SDK internals (executors, transports, etc.).
45+
46+
When you instantiate a `GraphQLClient`, you need to supply a `GraphQLExecutor` instance — a component responsible for orchestrating request execution and mapping errors to the SDK’s exception model. Under the hood, the `GraphQLExecutor` delegates to an `AbstractRequestExecutor` implementation from the core module, where you configure your request pipeline (authentication, logging, masking, etc.).
47+
48+
```mermaid
49+
flowchart LR
50+
GCI[MyGraphQLClient]
51+
GC[GraphQLClient]
52+
GE[GraphQLExecutor]
53+
ARE[AbstractRequestExecutor]
54+
55+
%% relationships
56+
GCI --> |extends| GC
57+
GC --> |uses| GE
58+
GE --> |uses| ARE
59+
```
60+
61+
### GraphQL Responses Processing
62+
In GraphQL, API responses fall into four categories:
63+
| Response Type | Description |
64+
|-------------------------------|--------------------------------------------------------------------------------------|
65+
| Successful response | All requested data is returned. |
66+
| Partial response | Some fields resolve successfully while others fail. |
67+
| Error response (no data) | The server processes the request but returns errors for every field, yielding no data. |
68+
| Exception response | A transport or server error occurs, and no GraphQL payload is returned. |
69+
70+
71+
Because GraphQL always uses an HTTP `2xx` status code, the SDK applies its own conventions in the `GraphQLExecutor` to make responses predictable:
72+
| Response Type | SDK Behavior |
73+
|-------------------------------|-------------------------------------------------------------------------------------------------------|
74+
| Exception response | Throws an `ExpediaGroupServiceException`. |
75+
| Partial response | Returns a `RawResponse` containing both the successfully resolved data and the error list. |
76+
| Error response (no data) | Throws a `NoDataException`, including the full response payload. |
77+
| Successful response | Returns a `RawResponse` with the data and an empty error list. |
78+
79+
80+
While similar to Apollo’s handling, this approach - especially throwing exceptions when no data is present — aligns with the broader SDK design and provides a consistent error‐handling model.
81+
82+
### Pagination
83+
To simplify working with cursor or offset‐based GraphQL pagination, the SDK provides two abstract utilities:
84+
85+
##### 1. PaginatedStream
86+
87+
A lazy, sequential stream of items from any paginated data source.
88+
89+
**How it works**:
90+
- Internally buffers one “page” of results at a time in a `Deque`.
91+
- Calls your implementation of `fetchNextPage(): List<T>?` whenever the buffer is empty.
92+
- Exposes a Java `Stream<T>` via `stream()`, so you can consume items one by one, without worrying about page boundaries.
93+
94+
95+
##### 2. Paginator
96+
A simple iterator‐style helper for paging through GraphQL responses that include both data and pageInfo in their model.
97+
98+
**Responsibilities:**
99+
- Tracks whether more pages remain via your `hasPagesToFetch()` implementation.
100+
- Implements the `Iterator<T>` interface
101+
- `hasNext()` returns true until no further pages remain.
102+
- `next()` should be provided by your subclass to fetch each page’s `PaginatedResponse`.
103+
104+
105+
106+
107+
108+
109+
110+
111+
112+
113+
114+
115+
116+

expediagroup-sdk-graphql/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ dependencies {
2828
testImplementation 'org.junit.jupiter:junit-jupiter-api'
2929
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
3030
testImplementation 'org.junit.jupiter:junit-jupiter-params'
31-
testImplementation 'io.mockk:mockk:1.14.4'
31+
testImplementation 'io.mockk:mockk:1.14.5'
3232
testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'
3333
testImplementation 'org.slf4j:slf4j-api:2.0.17'
3434
}

0 commit comments

Comments
 (0)