11# customer-service-client
22
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> ` ).
3+ Generated Java client for the demo ** customer-service** , showcasing ** type‑safe generic responses** with OpenAPI + a custom Mustache template (wrapping payloads in a reusable ` ApiClientResponse<T> ` ).
44
5- ---
6-
7- ## ✅ What you get
8-
9- * Generated code using ** OpenAPI Generator** (` restclient ` with Spring Framework ` RestClient ` )
10- * A thin wrapper class per endpoint (e.g. ` ApiResponseCustomerCreateResponse ` ) that ** extends** :
5+ This module demonstrates how to evolve OpenAPI Generator with minimal customization to support generic response envelopes — avoiding duplicated wrappers and preserving strong typing.
116
12- * ` src/main/java/com/example/demo/client/common/ApiClientResponse.java `
13- * Minimal Spring wiring to expose the generated API as beans:
7+ ---
148
15- * ` com.example.demo.client.adapter.config.CustomerApiClientConfig `
16- * A focused integration test with ** OkHttp MockWebServer** :
9+ ## ✅ What You Get
1710
18- * ` com.example.demo.client.adapter.CustomerClientIT `
11+ * Generated code using ** OpenAPI Generator** (` restclient ` with Spring Framework ` RestClient ` ).
12+ * A reusable generic base: ` io.github.bsayli.openapi.client.common.ApiClientResponse<T> ` .
13+ * Thin wrappers per endpoint (e.g. ` ApiResponseCustomerCreateResponse ` ) that extend the base.
14+ * Spring Boot configuration to auto‑expose the client as beans.
15+ * A focused integration test using ** OkHttp MockWebServer** .
1916
2017---
2118
22- ## 🧪 Quick pipeline (3 steps )
19+ ## 🚀 Quick Pipeline (3 Steps )
2320
24211 . ** Run the sample service**
2522
@@ -37,118 +34,158 @@ curl -s http://localhost:8084/customer/v3/api-docs.yaml \
3734 -o src/main/resources/customer-api-docs.yaml
3835```
3936
40- > You can also skip this if the spec is already checked in.
41-
42373 . ** Generate & build the client**
4338
4439``` bash
4540mvn clean install
4641```
4742
48- Generated sources will land under:
43+ Generated sources will be placed under:
4944
5045```
5146target/generated-sources/openapi/src/gen/java/main
5247```
5348
5449---
5550
56- ## 🚀 Using the client in your application
51+ ## 🧩 Using the Client
5752
58- ### Option A — Spring configuration (recommended)
53+ ### Option A — Spring Configuration (recommended)
5954
60- Add this module as a dependency and set the base URL. The module contributes a small configuration :
55+ Include this module as a dependency and configure the base URL:
6156
6257``` java
6358@Configuration
6459public class CustomerApiClientConfig {
60+
6561 @Bean
6662 public RestClient customerRestClient (RestClient .Builder builder ,
6763 @Value (" ${customer.api.base-url}" ) String baseUrl ) {
6864 return builder. baseUrl(baseUrl). build();
6965 }
7066
7167 @Bean
72- public com.example.demo .client.generated.invoker. ApiClient customerApiClient (
68+ public io.github.bsayli.openapi .client.generated.invoker. ApiClient customerApiClient (
7369 RestClient customerRestClient ,
7470 @Value (" ${customer.api.base-url}" ) String baseUrl ) {
75- return new com.example.demo .client.generated.invoker. ApiClient (customerRestClient)
71+ return new io.github.bsayli.openapi .client.generated.invoker. ApiClient (customerRestClient)
7672 .setBasePath(baseUrl);
7773 }
7874
7975 @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);
76+ public io.github.bsayli.openapi .client.generated.api. CustomerControllerApi customerControllerApi (
77+ io.github.bsayli.openapi .client.generated.invoker. ApiClient apiClient ) {
78+ return new io.github.bsayli.openapi .client.generated.api. CustomerControllerApi (apiClient);
8379 }
8480}
8581```
8682
87- ** Configure the base URL ** in your app:
83+ ** application.properties: **
8884
8985``` properties
9086customer.api.base-url =http://localhost:8084/customer
9187```
9288
93- ** Call the API ** :
89+ ** Usage example: **
9490
9591``` java
9692@Autowired
97- private com.example.demo .client.generated.api. CustomerControllerApi customerApi;
93+ private io.github.bsayli.openapi .client.generated.api. CustomerControllerApi customerApi;
9894
9995public void createCustomer() {
100- var req = new com.example.demo .client.generated.dto. CustomerCreateRequest ()
96+ var req = new io.github.bsayli.openapi .client.generated.dto. CustomerCreateRequest ()
10197 .name(" Jane Doe" )
10298 .email(" jane@example.com" );
10399
104100 var resp = customerApi. create(req); // ApiResponseCustomerCreateResponse
101+
105102 System . out. println(resp. getStatus()); // 201
106103 System . out. println(resp. getData(). getCustomer(). getName()); // "Jane Doe"
107104}
108105```
109106
110- ### Option B — Manual wiring (no Spring context)
107+ ### Option B — Manual Wiring (no Spring context)
111108
112109``` java
113110var rest = RestClient . builder(). baseUrl(" http://localhost:8084/customer" ). build();
114- var apiClient = new com.example.demo .client.generated.invoker. ApiClient (rest)
111+ var apiClient = new io.github.bsayli.openapi .client.generated.invoker. ApiClient (rest)
115112 .setBasePath(" http://localhost:8084/customer" );
116- var customerApi = new com.example.demo.client.generated.api. CustomerControllerApi (apiClient);
113+ var customerApi = new io.github.bsayli.openapi.client.generated.api. CustomerControllerApi (apiClient);
114+ ```
115+
116+ ---
117+
118+ ## 🧩 Adapter Pattern Example
119+
120+ For larger applications, encapsulate the generated API in an adapter:
121+
122+ ``` java
123+ package io.github.bsayli.openapi.client.adapter.impl ;
124+
125+ import io.github.bsayli.openapi.client.adapter.CustomerClientAdapter ;
126+ import io.github.bsayli.openapi.client.common.ApiClientResponse ;
127+ import io.github.bsayli.openapi.client.generated.api.CustomerControllerApi ;
128+ import io.github.bsayli.openapi.client.generated.dto.CustomerCreateRequest ;
129+ import io.github.bsayli.openapi.client.generated.dto.CustomerCreateResponse ;
130+ import org.springframework.stereotype.Service ;
131+
132+ @Service
133+ public class CustomerClientAdapterImpl implements CustomerClientAdapter {
134+
135+ private final CustomerControllerApi customerControllerApi;
136+
137+ public CustomerClientAdapterImpl (CustomerControllerApi customerControllerApi ) {
138+ this . customerControllerApi = customerControllerApi;
139+ }
140+
141+ @Override
142+ public ApiClientResponse<CustomerCreateResponse > create (CustomerCreateRequest request ) {
143+ return customerControllerApi. create(request);
144+ }
145+ }
117146```
118147
148+ This ensures:
149+
150+ * Generated code stays isolated.
151+ * Business code depends only on the adapter interface.
152+
119153---
120154
121- ## 🧩 How the generics work
155+ ## 🧩 How the Generics Work
122156
123- The template at ` src/main/resources/openapi-templates/api_wrapper.mustache ` emits thin wrappers like:
157+ The template at ` src/main/resources/openapi-templates/api_wrapper.mustache ` emits wrappers like:
124158
125159``` java
160+ import io.github.bsayli.openapi.client.common.ApiClientResponse ;
161+
126162// e.g., ApiResponseCustomerCreateResponse
127163public class ApiResponseCustomerCreateResponse
128- extends com.example.demo.client.common.ApiClientResponse<CustomerCreateResponse > { }
164+ extends ApiClientResponse<CustomerCreateResponse > {
165+ }
129166```
130167
131- Only ` api_wrapper.mustache ` is customized for this demo; ** all other models** use the stock templates/behavior .
168+ Only this Mustache partial is customized. All other models use stock templates.
132169
133170---
134171
135172## 🧪 Tests
136173
137- Run the integration-style test with MockWebServer:
174+ Integration test with MockWebServer:
138175
139176``` bash
140177mvn -q -DskipITs=false test
141178```
142179
143- It enqueues a ` 201 ` response and asserts mapping into ` ApiResponseCustomerCreateResponse ` .
180+ It enqueues a ` 201 ` response and asserts correct mapping into ` ApiResponseCustomerCreateResponse ` .
144181
145182---
146183
147184## 📚 Notes
148185
149- * Dependencies like ` spring-web ` , ` spring-context ` , ` jackson-* ` , ` jakarta.* ` are marked ** provided** . Your host app supplies them.
186+ * Dependencies like ` spring-web ` , ` spring-context ` , ` jackson-* ` , ` jakarta.* ` are marked ** provided** ; your host app supplies them.
150187* Generator options: Spring 6 ` RestClient ` , Jakarta EE, Jackson, Java 21.
151- * OpenAPI spec path used by the build :
188+ * OpenAPI spec path:
152189
153190```
154191src/main/resources/customer-api-docs.yaml
@@ -158,4 +195,4 @@ src/main/resources/customer-api-docs.yaml
158195
159196## 🛡 License
160197
161- This repository is licensed under ** MIT** (root ` LICENSE ` ). Submodules don’t duplicate license files; the root license applies .
198+ This repository is licensed under ** MIT** (root ` LICENSE ` ). Submodules inherit the license.
0 commit comments