Skip to content

Commit 28bdd24

Browse files
authored
Merge pull request #8 from bsayli/feature/response-modernization
Feature: Modernize API response architecture with RFC 7807 and generics-aware pagination
2 parents 01d9494 + 082288e commit 28bdd24

File tree

77 files changed

+4006
-2105
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+4006
-2105
lines changed

README.md

Lines changed: 181 additions & 241 deletions
Large diffs are not rendered by default.

customer-service-client/README.md

Lines changed: 126 additions & 490 deletions
Large diffs are not rendered by default.

customer-service-client/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>io.github.bsayli</groupId>
88
<artifactId>customer-service-client</artifactId>
9-
<version>0.6.8</version>
9+
<version>0.7.0</version>
1010
<name>customer-service-client</name>
1111
<description>Generated client (RestClient) using generics-aware OpenAPI templates</description>
1212
<packaging>jar</packaging>

customer-service-client/src/main/java/io/github/bsayli/openapi/client/adapter/CustomerClientAdapter.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
11
package io.github.bsayli.openapi.client.adapter;
22

3+
import io.github.bsayli.openapi.client.common.Page;
34
import io.github.bsayli.openapi.client.common.ServiceClientResponse;
4-
import io.github.bsayli.openapi.client.generated.dto.*;
5+
import io.github.bsayli.openapi.client.common.sort.SortDirection;
6+
import io.github.bsayli.openapi.client.common.sort.SortField;
7+
import io.github.bsayli.openapi.client.generated.dto.CustomerCreateRequest;
8+
import io.github.bsayli.openapi.client.generated.dto.CustomerDeleteResponse;
9+
import io.github.bsayli.openapi.client.generated.dto.CustomerDto;
10+
import io.github.bsayli.openapi.client.generated.dto.CustomerUpdateRequest;
511

612
public interface CustomerClientAdapter {
7-
ServiceClientResponse<CustomerCreateResponse> createCustomer(CustomerCreateRequest request);
13+
14+
ServiceClientResponse<CustomerDto> createCustomer(CustomerCreateRequest request);
815

916
ServiceClientResponse<CustomerDto> getCustomer(Integer customerId);
1017

11-
ServiceClientResponse<CustomerListResponse> getCustomers();
18+
ServiceClientResponse<Page<CustomerDto>> getCustomers();
19+
20+
ServiceClientResponse<Page<CustomerDto>> getCustomers(
21+
String name,
22+
String email,
23+
Integer page,
24+
Integer size,
25+
SortField sortBy,
26+
SortDirection direction);
1227

13-
ServiceClientResponse<CustomerUpdateResponse> updateCustomer(
28+
ServiceClientResponse<CustomerDto> updateCustomer(
1429
Integer customerId, CustomerUpdateRequest request);
1530

1631
ServiceClientResponse<CustomerDeleteResponse> deleteCustomer(Integer customerId);

customer-service-client/src/main/java/io/github/bsayli/openapi/client/adapter/config/CustomerApiClientConfig.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,41 @@
11
package io.github.bsayli.openapi.client.adapter.config;
22

3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import io.github.bsayli.openapi.client.common.error.ClientProblemException;
35
import io.github.bsayli.openapi.client.generated.api.CustomerControllerApi;
6+
import io.github.bsayli.openapi.client.generated.dto.ProblemDetail;
47
import io.github.bsayli.openapi.client.generated.invoker.ApiClient;
58
import java.time.Duration;
9+
import java.util.List;
610
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
711
import org.apache.hc.client5.http.impl.classic.HttpClients;
812
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
913
import org.springframework.beans.factory.annotation.Value;
14+
import org.springframework.boot.web.client.RestClientCustomizer;
1015
import org.springframework.context.annotation.Bean;
1116
import org.springframework.context.annotation.Configuration;
17+
import org.springframework.http.HttpStatusCode;
1218
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
1319
import org.springframework.web.client.RestClient;
1420

1521
@Configuration
1622
public class CustomerApiClientConfig {
1723

24+
@Bean
25+
RestClientCustomizer problemDetailStatusHandler(ObjectMapper om) {
26+
return builder ->
27+
builder.defaultStatusHandler(
28+
HttpStatusCode::isError,
29+
(request, response) -> {
30+
ProblemDetail pd = null;
31+
try (var is = response.getBody()) {
32+
pd = om.readValue(is, ProblemDetail.class);
33+
} catch (Exception ignore) {
34+
}
35+
throw new ClientProblemException(pd, response.getStatusCode().value());
36+
});
37+
}
38+
1839
@Bean(destroyMethod = "close")
1940
CloseableHttpClient customerHttpClient(
2041
@Value("${customer.api.max-connections-total:64}") int maxTotal,
@@ -51,8 +72,14 @@ HttpComponentsClientHttpRequestFactory customerRequestFactory(
5172

5273
@Bean
5374
RestClient customerRestClient(
54-
RestClient.Builder builder, HttpComponentsClientHttpRequestFactory customerRequestFactory) {
55-
return builder.requestFactory(customerRequestFactory).build();
75+
RestClient.Builder builder,
76+
HttpComponentsClientHttpRequestFactory customerRequestFactory,
77+
List<RestClientCustomizer> customizers) {
78+
builder.requestFactory(customerRequestFactory);
79+
if (customizers != null) {
80+
customizers.forEach(c -> c.customize(builder));
81+
}
82+
return builder.build();
5683
}
5784

5885
@Bean
Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,64 @@
11
package io.github.bsayli.openapi.client.adapter.impl;
22

33
import io.github.bsayli.openapi.client.adapter.CustomerClientAdapter;
4+
import io.github.bsayli.openapi.client.common.Page;
45
import io.github.bsayli.openapi.client.common.ServiceClientResponse;
6+
import io.github.bsayli.openapi.client.common.sort.SortDirection;
7+
import io.github.bsayli.openapi.client.common.sort.SortField;
58
import io.github.bsayli.openapi.client.generated.api.CustomerControllerApi;
6-
import io.github.bsayli.openapi.client.generated.dto.CustomerCreateRequest;
7-
import io.github.bsayli.openapi.client.generated.dto.CustomerCreateResponse;
8-
import io.github.bsayli.openapi.client.generated.dto.CustomerDeleteResponse;
9-
import io.github.bsayli.openapi.client.generated.dto.CustomerDto;
10-
import io.github.bsayli.openapi.client.generated.dto.CustomerListResponse;
11-
import io.github.bsayli.openapi.client.generated.dto.CustomerUpdateRequest;
12-
import io.github.bsayli.openapi.client.generated.dto.CustomerUpdateResponse;
9+
import io.github.bsayli.openapi.client.generated.dto.*;
1310
import org.springframework.stereotype.Service;
1411

1512
@Service
1613
public class CustomerClientAdapterImpl implements CustomerClientAdapter {
1714

18-
private final CustomerControllerApi customerControllerApi;
15+
private final CustomerControllerApi api;
1916

2017
public CustomerClientAdapterImpl(CustomerControllerApi customerControllerApi) {
21-
this.customerControllerApi = customerControllerApi;
18+
this.api = customerControllerApi;
2219
}
2320

2421
@Override
25-
public ServiceClientResponse<CustomerCreateResponse> createCustomer(
26-
CustomerCreateRequest request) {
27-
return customerControllerApi.createCustomer(request);
22+
public ServiceClientResponse<CustomerDto> createCustomer(CustomerCreateRequest request) {
23+
return api.createCustomer(request);
2824
}
2925

3026
@Override
3127
public ServiceClientResponse<CustomerDto> getCustomer(Integer customerId) {
32-
return customerControllerApi.getCustomer(customerId);
28+
return api.getCustomer(customerId);
3329
}
3430

3531
@Override
36-
public ServiceClientResponse<CustomerListResponse> getCustomers() {
37-
return customerControllerApi.getCustomers();
32+
public ServiceClientResponse<Page<CustomerDto>> getCustomers() {
33+
return getCustomers(null, null, 0, 5, SortField.CUSTOMER_ID, SortDirection.ASC);
3834
}
3935

4036
@Override
41-
public ServiceClientResponse<CustomerUpdateResponse> updateCustomer(
37+
public ServiceClientResponse<Page<CustomerDto>> getCustomers(
38+
String name,
39+
String email,
40+
Integer page,
41+
Integer size,
42+
SortField sortBy,
43+
SortDirection direction) {
44+
45+
return api.getCustomers(
46+
name,
47+
email,
48+
page,
49+
size,
50+
sortBy != null ? sortBy.value() : SortField.CUSTOMER_ID.value(),
51+
direction != null ? direction.value() : SortDirection.ASC.value());
52+
}
53+
54+
@Override
55+
public ServiceClientResponse<CustomerDto> updateCustomer(
4256
Integer customerId, CustomerUpdateRequest request) {
43-
return customerControllerApi.updateCustomer(customerId, request);
57+
return api.updateCustomer(customerId, request);
4458
}
4559

4660
@Override
4761
public ServiceClientResponse<CustomerDeleteResponse> deleteCustomer(Integer customerId) {
48-
return customerControllerApi.deleteCustomer(customerId);
62+
return api.deleteCustomer(customerId);
4963
}
5064
}

customer-service-client/src/main/java/io/github/bsayli/openapi/client/common/ClientErrorDetail.java

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.github.bsayli.openapi.client.common;
2+
3+
import io.github.bsayli.openapi.client.common.sort.ClientSort;
4+
import java.time.Instant;
5+
import java.util.List;
6+
7+
public record ClientMeta(Instant serverTime, List<ClientSort> sort) {
8+
public ClientMeta {
9+
sort = (sort == null) ? List.of() : List.copyOf(sort);
10+
}
11+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.github.bsayli.openapi.client.common;
2+
3+
import java.util.List;
4+
5+
public record Page<T>(
6+
List<T> content,
7+
int page,
8+
int size,
9+
long totalElements,
10+
int totalPages,
11+
boolean hasNext,
12+
boolean hasPrev) {
13+
14+
public Page {
15+
content = (content == null) ? List.of() : List.copyOf(content);
16+
}
17+
}
Lines changed: 16 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,21 @@
11
package io.github.bsayli.openapi.client.common;
22

3-
import java.util.List;
43
import java.util.Objects;
54

65
public class ServiceClientResponse<T> {
76

8-
private Integer status;
9-
private String message;
10-
private List<ClientErrorDetail> errors;
117
private T data;
8+
private ClientMeta meta;
129

1310
public ServiceClientResponse() {}
1411

15-
public ServiceClientResponse(
16-
Integer status, String message, List<ClientErrorDetail> errors, T data) {
17-
this.status = status;
18-
this.message = message;
19-
this.errors = errors;
12+
public ServiceClientResponse(T data, ClientMeta meta) {
2013
this.data = data;
14+
this.meta = meta;
2115
}
2216

23-
public static <T> ServiceClientResponse<T> from(
24-
Integer status, String message, List<ClientErrorDetail> errors, T data) {
25-
return new ServiceClientResponse<>(status, message, errors, data);
26-
}
27-
28-
public Integer getStatus() {
29-
return status;
30-
}
31-
32-
public void setStatus(Integer status) {
33-
this.status = status;
34-
}
35-
36-
public String getMessage() {
37-
return message;
38-
}
39-
40-
public void setMessage(String message) {
41-
this.message = message;
42-
}
43-
44-
public List<ClientErrorDetail> getErrors() {
45-
return errors;
46-
}
47-
48-
public void setErrors(List<ClientErrorDetail> errors) {
49-
this.errors = errors;
17+
public static <T> ServiceClientResponse<T> of(T data, ClientMeta meta) {
18+
return new ServiceClientResponse<>(data, meta);
5019
}
5120

5221
public T getData() {
@@ -57,33 +26,28 @@ public void setData(T data) {
5726
this.data = data;
5827
}
5928

29+
public ClientMeta getMeta() {
30+
return meta;
31+
}
32+
33+
public void setMeta(ClientMeta meta) {
34+
this.meta = meta;
35+
}
36+
6037
@Override
6138
public boolean equals(Object o) {
6239
if (this == o) return true;
6340
if (!(o instanceof ServiceClientResponse<?> that)) return false;
64-
return Objects.equals(status, that.status)
65-
&& Objects.equals(message, that.message)
66-
&& Objects.equals(errors, that.errors)
67-
&& Objects.equals(data, that.data);
41+
return Objects.equals(data, that.data) && Objects.equals(meta, that.meta);
6842
}
6943

7044
@Override
7145
public int hashCode() {
72-
return Objects.hash(status, message, errors, data);
46+
return Objects.hash(data, meta);
7347
}
7448

7549
@Override
7650
public String toString() {
77-
return "ServiceClientResponse{"
78-
+ "status="
79-
+ status
80-
+ ", message='"
81-
+ message
82-
+ '\''
83-
+ ", errors="
84-
+ errors
85-
+ ", data="
86-
+ data
87-
+ '}';
51+
return "ServiceClientResponse{data=" + data + ", meta=" + meta + '}';
8852
}
8953
}

0 commit comments

Comments
 (0)