mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-05-15 08:59:01 +00:00
deps: Refactor dependencies (#3224)
* remove spring dep move junit, logging, mockito under dep mgmt * upgrade anti-corruption-layer deps * async method invocation * balking, bloc * bridge to bytecode * caching * callback - cqrs * component - health check * hexagonal - metadata mapping * rest of the patterns * remove checkstyle, take spotless into use
This commit is contained in:
@@ -30,8 +30,8 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* This class represents an endpoint resource that
|
||||
* we are going to interchange with a Rest API server.
|
||||
* This class represents an endpoint resource that we are going to interchange with a Rest API
|
||||
* server.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@@ -42,5 +42,4 @@ public class Album {
|
||||
private Integer id;
|
||||
private String title;
|
||||
private Integer userId;
|
||||
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ import java.net.http.HttpClient;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Class whose method 'invoke' will be called every time that an interface's method is called.
|
||||
* That interface is linked to this class by the Proxy class.
|
||||
* Class whose method 'invoke' will be called every time that an interface's method is called. That
|
||||
* interface is linked to this class by the Proxy class.
|
||||
*/
|
||||
@Slf4j
|
||||
public class AlbumInvocationHandler implements InvocationHandler {
|
||||
@@ -42,7 +42,7 @@ public class AlbumInvocationHandler implements InvocationHandler {
|
||||
/**
|
||||
* Class constructor. It instantiates a TinyRestClient object.
|
||||
*
|
||||
* @param baseUrl Root url for endpoints.
|
||||
* @param baseUrl Root url for endpoints.
|
||||
* @param httpClient Handle the http communication.
|
||||
*/
|
||||
public AlbumInvocationHandler(String baseUrl, HttpClient httpClient) {
|
||||
@@ -52,10 +52,11 @@ public class AlbumInvocationHandler implements InvocationHandler {
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
|
||||
LOGGER.info("===== Calling the method {}.{}()",
|
||||
method.getDeclaringClass().getSimpleName(), method.getName());
|
||||
LOGGER.info(
|
||||
"===== Calling the method {}.{}()",
|
||||
method.getDeclaringClass().getSimpleName(),
|
||||
method.getName());
|
||||
|
||||
return restClient.send(method, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* Every method in this interface is annotated with the necessary metadata to represents an endpoint
|
||||
* that we can call to communicate with a host server which is serving a resource by Rest API.
|
||||
* This interface is focused in the resource Album.
|
||||
* that we can call to communicate with a host server which is serving a resource by Rest API. This
|
||||
* interface is focused in the resource Album.
|
||||
*/
|
||||
public interface AlbumService {
|
||||
|
||||
@@ -69,7 +69,7 @@ public interface AlbumService {
|
||||
* Updates an existing album.
|
||||
*
|
||||
* @param albumId Album's id to be modified.
|
||||
* @param album New album's data.
|
||||
* @param album New album's data.
|
||||
* @return Updated album's data.
|
||||
*/
|
||||
@Put("/albums/{albumId}")
|
||||
@@ -83,5 +83,4 @@ public interface AlbumService {
|
||||
*/
|
||||
@Delete("/albums/{albumId}")
|
||||
Album deleteAlbum(@Path("albumId") Integer albumId);
|
||||
|
||||
}
|
||||
|
||||
@@ -30,14 +30,14 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Application to demonstrate the Dynamic Proxy pattern. This application allow us to hit the public
|
||||
* fake API https://jsonplaceholder.typicode.com for the resource Album through an interface.
|
||||
* The call to Proxy.newProxyInstance creates a new dynamic proxy for the AlbumService interface and
|
||||
* fake API https://jsonplaceholder.typicode.com for the resource Album through an interface. The
|
||||
* call to Proxy.newProxyInstance creates a new dynamic proxy for the AlbumService interface and
|
||||
* sets the AlbumInvocationHandler class as the handler to intercept all the interface's methods.
|
||||
* Everytime that we call an AlbumService's method, the handler's method "invoke" will be call
|
||||
* automatically, and it will pass all the method's metadata and arguments to other specialized
|
||||
* class - TinyRestClient - to prepare the Rest API call accordingly.
|
||||
* In this demo, the Dynamic Proxy pattern help us to run business logic through interfaces without
|
||||
* an explicit implementation of the interfaces and supported on the Java Reflection approach.
|
||||
* class - TinyRestClient - to prepare the Rest API call accordingly. In this demo, the Dynamic
|
||||
* Proxy pattern help us to run business logic through interfaces without an explicit implementation
|
||||
* of the interfaces and supported on the Java Reflection approach.
|
||||
*/
|
||||
@Slf4j
|
||||
public class App {
|
||||
@@ -51,7 +51,7 @@ public class App {
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param baseUrl Root url for endpoints.
|
||||
* @param baseUrl Root url for endpoints.
|
||||
* @param httpClient Handle the http communication.
|
||||
*/
|
||||
public App(String baseUrl, HttpClient httpClient) {
|
||||
@@ -71,18 +71,23 @@ public class App {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Dynamic Proxy linked to the AlbumService interface and to the AlbumInvocationHandler.
|
||||
* Create the Dynamic Proxy linked to the AlbumService interface and to the
|
||||
* AlbumInvocationHandler.
|
||||
*/
|
||||
public void createDynamicProxy() {
|
||||
AlbumInvocationHandler albumInvocationHandler = new AlbumInvocationHandler(baseUrl, httpClient);
|
||||
|
||||
albumServiceProxy = (AlbumService) Proxy.newProxyInstance(
|
||||
App.class.getClassLoader(), new Class<?>[]{AlbumService.class}, albumInvocationHandler);
|
||||
albumServiceProxy =
|
||||
(AlbumService)
|
||||
Proxy.newProxyInstance(
|
||||
App.class.getClassLoader(),
|
||||
new Class<?>[] {AlbumService.class},
|
||||
albumInvocationHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the methods of the Dynamic Proxy, in other words, the AlbumService interface's methods
|
||||
* and receive the responses from the Rest API.
|
||||
* Call the methods of the Dynamic Proxy, in other words, the AlbumService interface's methods and
|
||||
* receive the responses from the Rest API.
|
||||
*/
|
||||
public void callMethods() {
|
||||
int albumId = 17;
|
||||
@@ -94,16 +99,16 @@ public class App {
|
||||
var album = albumServiceProxy.readAlbum(albumId);
|
||||
LOGGER.info("{}", album);
|
||||
|
||||
var newAlbum = albumServiceProxy.createAlbum(Album.builder()
|
||||
.title("Big World").userId(userId).build());
|
||||
var newAlbum =
|
||||
albumServiceProxy.createAlbum(Album.builder().title("Big World").userId(userId).build());
|
||||
LOGGER.info("{}", newAlbum);
|
||||
|
||||
var editAlbum = albumServiceProxy.updateAlbum(albumId, Album.builder()
|
||||
.title("Green Valley").userId(userId).build());
|
||||
var editAlbum =
|
||||
albumServiceProxy.updateAlbum(
|
||||
albumId, Album.builder().title("Green Valley").userId(userId).build());
|
||||
LOGGER.info("{}", editAlbum);
|
||||
|
||||
var removedAlbum = albumServiceProxy.deleteAlbum(albumId);
|
||||
LOGGER.info("{}", removedAlbum);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,22 +32,19 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Utility class to handle Json operations.
|
||||
*/
|
||||
/** Utility class to handle Json operations. */
|
||||
@Slf4j
|
||||
public class JsonUtil {
|
||||
|
||||
private static ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private JsonUtil() {
|
||||
}
|
||||
private JsonUtil() {}
|
||||
|
||||
/**
|
||||
* Convert an object to a Json string representation.
|
||||
*
|
||||
* @param object Object to convert.
|
||||
* @param <T> Object's class.
|
||||
* @param <T> Object's class.
|
||||
* @return Json string.
|
||||
*/
|
||||
public static <T> String objectToJson(T object) {
|
||||
@@ -62,9 +59,9 @@ public class JsonUtil {
|
||||
/**
|
||||
* Convert a Json string to an object of a class.
|
||||
*
|
||||
* @param json Json string to convert.
|
||||
* @param json Json string to convert.
|
||||
* @param clazz Object's class.
|
||||
* @param <T> Object's generic class.
|
||||
* @param <T> Object's generic class.
|
||||
* @return Object.
|
||||
*/
|
||||
public static <T> T jsonToObject(String json, Class<T> clazz) {
|
||||
@@ -79,20 +76,19 @@ public class JsonUtil {
|
||||
/**
|
||||
* Convert a Json string to a List of objects of a class.
|
||||
*
|
||||
* @param json Json string to convert.
|
||||
* @param json Json string to convert.
|
||||
* @param clazz Object's class.
|
||||
* @param <T> Object's generic class.
|
||||
* @param <T> Object's generic class.
|
||||
* @return List of objects.
|
||||
*/
|
||||
public static <T> List<T> jsonToList(String json, Class<T> clazz) {
|
||||
try {
|
||||
CollectionType listType = objectMapper.getTypeFactory()
|
||||
.constructCollectionType(ArrayList.class, clazz);
|
||||
CollectionType listType =
|
||||
objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, clazz);
|
||||
return objectMapper.reader().forType(listType).readValue(json);
|
||||
} catch (JsonProcessingException e) {
|
||||
LOGGER.error("Cannot convert the Json " + json + " to List of " + clazz.getName() + ".", e);
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+34
-22
@@ -45,8 +45,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
/**
|
||||
* Class to handle all the http communication with a Rest API.
|
||||
* It is supported by the HttpClient Java library.
|
||||
* Class to handle all the http communication with a Rest API. It is supported by the HttpClient
|
||||
* Java library.
|
||||
*/
|
||||
@Slf4j
|
||||
public class TinyRestClient {
|
||||
@@ -59,7 +59,7 @@ public class TinyRestClient {
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param baseUrl Root url for endpoints.
|
||||
* @param baseUrl Root url for endpoints.
|
||||
* @param httpClient Handle the http communication.
|
||||
*/
|
||||
public TinyRestClient(String baseUrl, HttpClient httpClient) {
|
||||
@@ -71,9 +71,9 @@ public class TinyRestClient {
|
||||
* Creates a http communication to request and receive data from an endpoint.
|
||||
*
|
||||
* @param method Interface's method which is annotated with a http method.
|
||||
* @param args Method's arguments passed in the call.
|
||||
* @param args Method's arguments passed in the call.
|
||||
* @return Response from the endpoint.
|
||||
* @throws IOException Exception thrown when any fail happens in the call.
|
||||
* @throws IOException Exception thrown when any fail happens in the call.
|
||||
* @throws InterruptedException Exception thrown when call is interrupted.
|
||||
*/
|
||||
public Object send(Method method, Object[] args) throws IOException, InterruptedException {
|
||||
@@ -84,11 +84,12 @@ public class TinyRestClient {
|
||||
var httpAnnotationName = httpAnnotation.annotationType().getSimpleName().toUpperCase();
|
||||
var url = baseUrl + buildUrl(method, args, httpAnnotation);
|
||||
var bodyPublisher = buildBodyPublisher(method, args);
|
||||
var httpRequest = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.header("Content-Type", "application/json")
|
||||
.method(httpAnnotationName, bodyPublisher)
|
||||
.build();
|
||||
var httpRequest =
|
||||
HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.header("Content-Type", "application/json")
|
||||
.method(httpAnnotationName, bodyPublisher)
|
||||
.build();
|
||||
var httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
|
||||
var statusCode = httpResponse.statusCode();
|
||||
if (statusCode >= HttpURLConnection.HTTP_BAD_REQUEST) {
|
||||
@@ -140,8 +141,8 @@ public class TinyRestClient {
|
||||
return null;
|
||||
}
|
||||
if (returnType instanceof ParameterizedType) {
|
||||
Class<?> responseClass = (Class<?>) (((ParameterizedType) returnType)
|
||||
.getActualTypeArguments()[0]);
|
||||
Class<?> responseClass =
|
||||
(Class<?>) (((ParameterizedType) returnType).getActualTypeArguments()[0]);
|
||||
return JsonUtil.jsonToList(rawData, responseClass);
|
||||
} else {
|
||||
Class<?> responseClass = method.getReturnType();
|
||||
@@ -150,22 +151,28 @@ public class TinyRestClient {
|
||||
}
|
||||
|
||||
private Annotation getHttpAnnotation(Method method) {
|
||||
return httpAnnotationByMethod.computeIfAbsent(method, m ->
|
||||
Arrays.stream(m.getDeclaredAnnotations())
|
||||
.filter(annot -> annot.annotationType().isAnnotationPresent(Http.class))
|
||||
.findFirst().orElse(null));
|
||||
return httpAnnotationByMethod.computeIfAbsent(
|
||||
method,
|
||||
m ->
|
||||
Arrays.stream(m.getDeclaredAnnotations())
|
||||
.filter(annot -> annot.annotationType().isAnnotationPresent(Http.class))
|
||||
.findFirst()
|
||||
.orElse(null));
|
||||
}
|
||||
|
||||
private Annotation getAnnotationOf(Annotation[] annotations, Class<?> clazz) {
|
||||
return Arrays.stream(annotations)
|
||||
.filter(annot -> annot.annotationType().equals(clazz))
|
||||
.findFirst().orElse(null);
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private String annotationValue(Annotation annotation) {
|
||||
var valueMethod = Arrays.stream(annotation.annotationType().getDeclaredMethods())
|
||||
.filter(methodAnnot -> methodAnnot.getName().equals("value"))
|
||||
.findFirst().orElse(null);
|
||||
var valueMethod =
|
||||
Arrays.stream(annotation.annotationType().getDeclaredMethods())
|
||||
.filter(methodAnnot -> methodAnnot.getName().equals("value"))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (valueMethod == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -173,8 +180,13 @@ public class TinyRestClient {
|
||||
try {
|
||||
result = valueMethod.invoke(annotation, (Object[]) null);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Cannot read the value " + annotation.annotationType().getSimpleName()
|
||||
+ "." + valueMethod.getName() + "()", e);
|
||||
LOGGER.error(
|
||||
"Cannot read the value "
|
||||
+ annotation.annotationType().getSimpleName()
|
||||
+ "."
|
||||
+ valueMethod.getName()
|
||||
+ "()",
|
||||
e);
|
||||
result = null;
|
||||
}
|
||||
return (result instanceof String strResult ? strResult : null);
|
||||
|
||||
+3
-4
@@ -30,10 +30,9 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to mark a method's parameter as a Body parameter.
|
||||
* It is typically used on Post and Put http methods.
|
||||
* Annotation to mark a method's parameter as a Body parameter. It is typically used on Post and Put
|
||||
* http methods.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.PARAMETER)
|
||||
public @interface Body {
|
||||
}
|
||||
public @interface Body {}
|
||||
|
||||
+1
-3
@@ -29,9 +29,7 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to mark an interface's method as a DELETE http method.
|
||||
*/
|
||||
/** Annotation to mark an interface's method as a DELETE http method. */
|
||||
@Http
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
|
||||
+1
-3
@@ -29,9 +29,7 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to mark an interface's method as a GET http method.
|
||||
*/
|
||||
/** Annotation to mark an interface's method as a GET http method. */
|
||||
@Http
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
|
||||
+2
-5
@@ -29,10 +29,7 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to mark other annotations to be recognized as http methods.
|
||||
*/
|
||||
/** Annotation to mark other annotations to be recognized as http methods. */
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.ANNOTATION_TYPE)
|
||||
public @interface Http {
|
||||
}
|
||||
public @interface Http {}
|
||||
|
||||
+1
-3
@@ -29,9 +29,7 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to mark a method's parameter as a Path parameter.
|
||||
*/
|
||||
/** Annotation to mark a method's parameter as a Path parameter. */
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.PARAMETER)
|
||||
public @interface Path {
|
||||
|
||||
+1
-3
@@ -29,9 +29,7 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to mark an interface's method as a POST http method.
|
||||
*/
|
||||
/** Annotation to mark an interface's method as a POST http method. */
|
||||
@Http
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
|
||||
+1
-3
@@ -29,9 +29,7 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to mark an interface's method as a PUT http method.
|
||||
*/
|
||||
/** Annotation to mark an interface's method as a PUT http method. */
|
||||
@Http
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
|
||||
Reference in New Issue
Block a user