|
1 | 1 | # customer-service-client |
2 | 2 |
|
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>`). |
5 | 4 |
|
6 | 5 | --- |
7 | 6 |
|
8 | | -## 📦 Contents |
| 7 | +## ✅ What you get |
9 | 8 |
|
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` |
14 | 19 |
|
15 | 20 | --- |
16 | 21 |
|
17 | | -## 🔧 Requirements |
| 22 | +## 🧪 Quick pipeline (3 steps) |
18 | 23 |
|
19 | | -* JDK 21 |
20 | | -* Maven 3.9+ |
21 | | -* OpenAPI spec at: |
| 24 | +1. **Run the sample service** |
22 | 25 |
|
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 |
25 | 30 | ``` |
26 | 31 |
|
27 | | -To refresh from a running service: |
| 32 | +2. **Pull the OpenAPI spec into this module** |
28 | 33 |
|
29 | 34 | ```bash |
| 35 | +cd customer-service-client |
30 | 36 | curl -s http://localhost:8084/customer/v3/api-docs.yaml \ |
31 | 37 | -o src/main/resources/customer-api-docs.yaml |
32 | 38 | ``` |
33 | 39 |
|
34 | | ---- |
35 | | - |
36 | | -## 🚀 Build & Generate |
| 40 | +> You can also skip this if the spec is already checked in. |
37 | 41 |
|
38 | | -Run: |
| 42 | +3. **Generate & build the client** |
39 | 43 |
|
40 | 44 | ```bash |
41 | 45 | mvn clean install |
42 | 46 | ``` |
43 | 47 |
|
44 | | -Generated sources land in: |
| 48 | +Generated sources will land under: |
45 | 49 |
|
46 | 50 | ``` |
47 | 51 | target/generated-sources/openapi/src/gen/java/main |
48 | 52 | ``` |
49 | 53 |
|
50 | 54 | --- |
51 | 55 |
|
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 |
70 | 57 |
|
71 | | -Run tests with: |
| 58 | +### Option A — Spring configuration (recommended) |
72 | 59 |
|
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: |
82 | 61 |
|
83 | 62 | ```java |
84 | 63 | @Configuration |
85 | 64 | public class CustomerApiClientConfig { |
86 | 65 | @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) { |
89 | 68 | return builder.baseUrl(baseUrl).build(); |
90 | 69 | } |
91 | 70 |
|
92 | 71 | @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); |
95 | 83 | } |
96 | 84 | } |
97 | 85 | ``` |
98 | 86 |
|
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**: |
100 | 94 |
|
101 | 95 | ```java |
102 | 96 | @Autowired |
103 | | -private CustomerControllerApi customerApi; |
| 97 | +private com.example.demo.client.generated.api.CustomerControllerApi customerApi; |
104 | 98 |
|
105 | 99 | 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 | + |
| 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" |
109 | 107 | } |
110 | 108 | ``` |
111 | 109 |
|
112 | | -Property required: |
| 110 | +### Option B — Manual wiring (no Spring context) |
113 | 111 |
|
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); |
116 | 117 | ``` |
117 | 118 |
|
118 | 119 | --- |
119 | 120 |
|
| 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 | + |
120 | 147 | ## 📚 Notes |
121 | 148 |
|
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: |
124 | 152 |
|
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 | +``` |
128 | 156 |
|
129 | 157 | --- |
130 | 158 |
|
131 | 159 | ## 🛡 License |
132 | 160 |
|
133 | | -MIT |
| 161 | +This repository is licensed under **MIT** (root `LICENSE`). Submodules don’t duplicate license files; the root license applies. |
0 commit comments