Skip to content

Commit 829b48f

Browse files
committed
README.md files were updated.
1 parent 1b4a014 commit 829b48f

File tree

2 files changed

+98
-70
lines changed

2 files changed

+98
-70
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
# spring-boot-openapi-generics-clients
22

33
**Type-safe client generation with Spring Boot & OpenAPI using generics.**
4-
This repository demonstrates how to teach OpenAPI Generator to work with generics in order to avoid boilerplate, reduce
5-
duplicated wrappers, and keep client code clean.
4+
This repository demonstrates how to teach OpenAPI Generator to work with generics in order to avoid boilerplate, reduce duplicated wrappers, and keep client code clean.
65

76
---
87

98
## 🚀 Problem Statement
109

1110
Most backend teams standardize responses with a generic wrapper like `ApiResponse<T>`.
12-
However, **OpenAPI Generator does not natively support generics** — instead, it generates one wrapper per endpoint (
13-
duplicating fields like `status`, `message`, and `errors`).
11+
However, **OpenAPI Generator does not natively support generics** — instead, it generates one wrapper per endpoint (duplicating fields like `status`, `message`, and `errors`).
1412
This creates:
1513

1614
* ❌ Dozens of almost-identical classes
@@ -58,17 +56,19 @@ spring-boot-openapi-generics-clients/
5856
cd customer-service
5957
mvn spring-boot:run
6058
```
59+
6160
2. **Generate the client**
6261

6362
```bash
6463
cd customer-service-client
6564
mvn clean install
6665
```
66+
6767
3. **Use the generated API**
6868

6969
```java
7070
ApiClientResponse<CustomerCreateResponse> response =
71-
customerControllerApi.createCustomer(request);
71+
customerControllerApi.create(request);
7272
```
7373

7474
---

customer-service-client/README.md

Lines changed: 93 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,161 @@
11
# customer-service-client
22

3-
A minimal Java client for the demo **customer-service**, showing how to use **type-safe generic responses** with OpenAPI
4-
and a custom template (`ApiClientResponse<T>`).
3+
Generated Java client for the demo **customer-service**, showcasing **type‑safe generic responses** with OpenAPI + a tiny custom template (wrapping payloads in a reusable `ApiClientResponse<T>`).
54

65
---
76

8-
## 📦 Contents
7+
## ✅ What you get
98

10-
* Generated sources via **OpenAPI Generator** (`restclient` using Spring `RestClient`)
11-
* Custom wrapper generation using `ApiClientResponse<T>`
12-
* Spring config for wiring (`CustomerApiClientConfig`)
13-
* Integration test with **MockWebServer** (`CustomerClientIT`)
9+
* Generated code using **OpenAPI Generator** (`restclient` with Spring Framework `RestClient`)
10+
* A thin wrapper class per endpoint (e.g. `ApiResponseCustomerCreateResponse`) that **extends**:
11+
12+
* `src/main/java/com/example/demo/client/common/ApiClientResponse.java`
13+
* Minimal Spring wiring to expose the generated API as beans:
14+
15+
* `com.example.demo.client.adapter.config.CustomerApiClientConfig`
16+
* A focused integration test with **OkHttp MockWebServer**:
17+
18+
* `com.example.demo.client.adapter.CustomerClientIT`
1419

1520
---
1621

17-
## 🔧 Requirements
22+
## 🧪 Quick pipeline (3 steps)
1823

19-
* JDK 21
20-
* Maven 3.9+
21-
* OpenAPI spec at:
24+
1. **Run the sample service**
2225

23-
```
24-
src/main/resources/customer-api-docs.yaml
26+
```bash
27+
cd customer-service
28+
mvn spring-boot:run
29+
# Service base URL: http://localhost:8084/customer
2530
```
2631

27-
To refresh from a running service:
32+
2. **Pull the OpenAPI spec into this module**
2833

2934
```bash
35+
cd customer-service-client
3036
curl -s http://localhost:8084/customer/v3/api-docs.yaml \
3137
-o src/main/resources/customer-api-docs.yaml
3238
```
3339

34-
---
35-
36-
## 🚀 Build & Generate
40+
> You can also skip this if the spec is already checked in.
3741
38-
Run:
42+
3. **Generate & build the client**
3943

4044
```bash
4145
mvn clean install
4246
```
4347

44-
Generated sources land in:
48+
Generated sources will land under:
4549

4650
```
4751
target/generated-sources/openapi/src/gen/java/main
4852
```
4953

5054
---
5155

52-
## 🧩 Generics support
53-
54-
Custom templates ensure wrappers extend one reusable class:
55-
56-
```java
57-
public class ApiResponseCustomerCreateResponse
58-
extends com.example.demo.client.common.ApiClientResponse<CustomerCreateResponse> {
59-
}
60-
```
61-
62-
This keeps response envelopes consistent and type-safe.
63-
64-
---
65-
66-
## 🧪 Tests
67-
68-
Integration test `CustomerClientIT` runs against a **MockWebServer**, verifying that a `201 CREATED` response maps
69-
correctly into `ApiResponseCustomerCreateResponse`.
56+
## 🚀 Using the client in your application
7057

71-
Run tests with:
58+
### Option A — Spring configuration (recommended)
7259

73-
```bash
74-
mvn test
75-
```
76-
77-
---
78-
79-
## 🧰 Usage
80-
81-
Add config to your application:
60+
Add this module as a dependency and set the base URL. The module contributes a small configuration:
8261

8362
```java
8463
@Configuration
8564
public class CustomerApiClientConfig {
8665
@Bean
87-
RestClient customerRestClient(RestClient.Builder builder,
88-
@Value("${customer.api.base-url}") String baseUrl) {
66+
public RestClient customerRestClient(RestClient.Builder builder,
67+
@Value("${customer.api.base-url}") String baseUrl) {
8968
return builder.baseUrl(baseUrl).build();
9069
}
9170

9271
@Bean
93-
CustomerControllerApi customerControllerApi(ApiClient apiClient) {
94-
return new CustomerControllerApi(apiClient);
72+
public com.example.demo.client.generated.invoker.ApiClient customerApiClient(
73+
RestClient customerRestClient,
74+
@Value("${customer.api.base-url}") String baseUrl) {
75+
return new com.example.demo.client.generated.invoker.ApiClient(customerRestClient)
76+
.setBasePath(baseUrl);
77+
}
78+
79+
@Bean
80+
public com.example.demo.client.generated.api.CustomerControllerApi customerControllerApi(
81+
com.example.demo.client.generated.invoker.ApiClient apiClient) {
82+
return new com.example.demo.client.generated.api.CustomerControllerApi(apiClient);
9583
}
9684
}
9785
```
9886

99-
Use in code:
87+
**Configure the base URL** in your app:
88+
89+
```properties
90+
customer.api.base-url=http://localhost:8084/customer
91+
```
92+
93+
**Call the API**:
10094

10195
```java
10296
@Autowired
103-
private CustomerControllerApi customerApi;
97+
private com.example.demo.client.generated.api.CustomerControllerApi customerApi;
10498

10599
public void createCustomer() {
106-
var req = new CustomerCreateRequest().name("Jane Doe").email("[email protected]");
107-
var resp = customerApi.create(req);
108-
System.out.println(resp.getStatus()); // 201
100+
var req = new com.example.demo.client.generated.dto.CustomerCreateRequest()
101+
.name("Jane Doe")
102+
.email("[email protected]");
103+
104+
var resp = customerApi.create(req); // ApiResponseCustomerCreateResponse
105+
System.out.println(resp.getStatus()); // 201
106+
System.out.println(resp.getData().getCustomer().getName()); // "Jane Doe"
109107
}
110108
```
111109

112-
Property required:
110+
### Option B — Manual wiring (no Spring context)
113111

114-
```properties
115-
customer.api.base-url=http://localhost:8084/customer
112+
```java
113+
var rest = RestClient.builder().baseUrl("http://localhost:8084/customer").build();
114+
var apiClient = new com.example.demo.client.generated.invoker.ApiClient(rest)
115+
.setBasePath("http://localhost:8084/customer");
116+
var customerApi = new com.example.demo.client.generated.api.CustomerControllerApi(apiClient);
116117
```
117118

118119
---
119120

121+
## 🧩 How the generics work
122+
123+
The template at `src/main/resources/openapi-templates/api_wrapper.mustache` emits thin wrappers like:
124+
125+
```java
126+
// e.g., ApiResponseCustomerCreateResponse
127+
public class ApiResponseCustomerCreateResponse
128+
extends com.example.demo.client.common.ApiClientResponse<CustomerCreateResponse> { }
129+
```
130+
131+
Only `api_wrapper.mustache` is customized for this demo; **all other models** use the stock templates/behavior.
132+
133+
---
134+
135+
## 🧪 Tests
136+
137+
Run the integration-style test with MockWebServer:
138+
139+
```bash
140+
mvn -q -DskipITs=false test
141+
```
142+
143+
It enqueues a `201` response and asserts mapping into `ApiResponseCustomerCreateResponse`.
144+
145+
---
146+
120147
## 📚 Notes
121148

122-
* Core deps (`spring-web`, `spring-context`, `jackson-*`, `jakarta.*`) are **provided**.
123-
* Custom templates:
149+
* Dependencies like `spring-web`, `spring-context`, `jackson-*`, `jakarta.*` are marked **provided**. Your host app supplies them.
150+
* Generator options: Spring 6 `RestClient`, Jakarta EE, Jackson, Java 21.
151+
* OpenAPI spec path used by the build:
124152

125-
* `api_wrapper.mustache` (main customization)
126-
* `model.mustache` (delegates to wrapper conditionally)
127-
* Others use stock OpenAPI Generator templates.
153+
```
154+
src/main/resources/customer-api-docs.yaml
155+
```
128156

129157
---
130158

131159
## 🛡 License
132160

133-
MIT
161+
This repository is licensed under **MIT** (root `LICENSE`). Submodules don’t duplicate license files; the root license applies.

0 commit comments

Comments
 (0)