From 9701a2f681fc8ef11dca7351e5ee441919bcbc70 Mon Sep 17 00:00:00 2001 From: Vicaria Barker Date: Mon, 29 Nov 2021 15:30:35 +0100 Subject: [PATCH 1/9] refactored rest guides --- .../decision-service-framework.asciidoc | 35 +-- documentation/guide-service-client.asciidoc | 273 +---------------- .../guide-service-client-spring.asciidoc | 279 ++++++++++++++++++ 3 files changed, 290 insertions(+), 297 deletions(-) create mode 100644 documentation/spring/guide-service-client-spring.asciidoc diff --git a/documentation/decision-service-framework.asciidoc b/documentation/decision-service-framework.asciidoc index 85134db3..a12ebfd1 100644 --- a/documentation/decision-service-framework.asciidoc +++ b/documentation/decision-service-framework.asciidoc @@ -3,40 +3,13 @@ toc::[] = Decision Sheet for Choosing a Service Framework -We need to choose which framework(s) we are using to build services. For the devonfw we focus on a standard API if available. However, we also want to recommend an implementation to use. While projects would still be able to choose whatever they want, we want to suggest the best, most robust and established solution. This way projects do not have to worry about the decision and can rely on a production ready framework without getting into trouble. Also besides the standard the configuration of the implementation framework differs and we want to give instructions how to do this in the documentation and by our sample application. This is why in the end the implementation also matters. If a project has a customer demand to use something else then the project has to take care of this. We will always suggest and "support" ONE solution. +We need to choose which framework(s) we want to use for building services. For the devonfw, we focus on a standard API, if available. However, we also want to recommend an implementation. While projects would still be able to choose whatever they want, we want to suggest the best, most robust, and established solution. This way, projects do not have to worry about the decision and can rely on a production-ready framework without running into any trouble. Also, besides the standard, the configuration of the implementation framework differs, so we want to give instructions in the documentation and by our sample application. This is why, in the end, the implementation also matters. If a project has a customer demand to use something else, the project has to take care of it. We will always suggest and "support" ONE solution. == REST Services -For REST services we rely on the JAX-RS standard (and NOT on spring-mvc with its proprietary annotations). -As JAX-RS implementation had Jersey (Reference-Implementation) and Apache CXF on the short-list. As it turned out, Jersey is build for HK2 and the spring integration is rather a quickfix. So we ran into bugs when we used it with spring. There are various issues open in the jersey tracker related to this. E.g. https://java.net/jira/browse/JERSEY-2112 -For Apache CXF the spring container was first choice but container abstraction has been properly introduced by design so it can be used in JEE application servers. Everything works smooth in our sample application and we collected feedback from various projects doing JAX-RS based on CXF either with XML or JSON with success in production. +For REST services, devonfw relies on the JAX-RS standard (and NOT on spring-mvc with its proprietary annotations). https://github.com/jax-rs[JAX-RS] (Java API for RESTful Web Services) is a Java programming language API to develop web services following the Representational State Transfer (REST) architectural pattern. +For Apache CXF, the spring container was the first choice, but container abstraction has been properly introduced by design, so it can be used in JEE application servers. Apache CXF is a services framework that helps to build and develop services using frontend programming APIs, such as JAX-RS (https://cxf.apache.org/). Everything works smoothly in our sample application, and in addition, we collected feedback from various projects utilizing CXF, either with XML or JSON, with reported success in production. Therefore, we decided to use Apache CXF (here | for Spring). +For Quarkus applications, Devon4j recommends to use RESTEasy. RESTEasy is a JAX-RS implementation aimed at providing productivity frameworks for developing client and server RESTful applications and services in Java (https://github.com/resteasy/resteasy) -Therefore we decided for Apache CXF here. - -Update: We still get questions like "Why don't you use `resteasy` or `dropwizard`?" - -To give a plain answer here you can see the stacktrace of a productive system using `resteasy` and failing to invoke a service: -``` -Caused by: javax.ws.rs.NotFoundException: HTTP 404 Not Found - at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.handleErrorStatus(ClientInvocation.java:200) - at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.extractResult(ClientInvocation.java:171) - at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.invoke(ClientInvocation.java:480) - at org.jboss.resteasy.client.jaxrs.internal.ClientInvocationBuilder.get(ClientInvocationBuilder.java:169) -``` - -This is an error you get when using `devon4j` with CXF: -``` -net.sf.mmm.util.exception.api.ServiceInvocationFailedException: 404: While invoking the service com.devonfw.test.app.myexample.service.api.rest.MyExampleRestService#greet[http://localhost:49468/app/services-404/rest/my-example/v1/greet/John%20Doe] the following error occurred: Not Found. Probably the service is temporary unavailable. Please try again later. If the problem persists contact your system administrator. -4fb68fd0-a962-46ab-8655-99cb9e43c9ca - at com.devonfw.module.service.common.impl.client.ServiceClientErrorFactoryImpl.create(ServiceClientErrorFactoryImpl.java:30) - at com.devonfw.module.service.common.base.client.AbstractServiceClientErrorFactory.createException(AbstractServiceClientErrorFactory.java:83) - at com.devonfw.module.service.common.base.client.AbstractServiceClientErrorFactory.unmarshall(AbstractServiceClientErrorFactory.java:65) - at com.devonfw.module.cxf.common.impl.client.rest.RestServiceExceptionMapper.fromResponse(RestServiceExceptionMapper.java:59) - at org.apache.cxf.jaxrs.client.ClientProxyImpl.checkResponse(ClientProxyImpl.java:422) -``` -On success you will instead get such log message: -``` -Invoking service com.devonfw.test.app.myexample.service.api.rest.MyExampleRestService#greet[http://localhost:50504/app/services/rest] took PT0.220281558S (220281558ns) and succeded with status 200. -``` == WebServices For WebServices we rely on the JAX-WS standard. On our short list we have https://metro.java.net[Metro2] and http://cxf.apache.org[Apache CXF]. Here a collection of facts and considerations: diff --git a/documentation/guide-service-client.asciidoc b/documentation/guide-service-client.asciidoc index 9127e9d5..d5126a57 100644 --- a/documentation/guide-service-client.asciidoc +++ b/documentation/guide-service-client.asciidoc @@ -4,276 +4,17 @@ toc::[] = Service Client -This guide is about consuming (calling) services from other applications (micro-services). For providing services see the link:guide-service-layer.asciidoc[Service-Layer Guide]. Services can be consumed in the link:guide-client-layer.asciidoc[client] or the server. As the client is typically not written in Java you should consult the according guide for your client technology. In case you want to call a service within your Java code this guide is the right place to get help. +This guide is about consuming (calling) services from other applications (micro-services). For providing services, see the link:guide-service-layer.asciidoc[Service-Layer Guide]. Services can be consumed in the link:guide-client-layer.asciidoc[client] or the server. As the client is typically not written in Java, you should consult the according guide for your client technology. In case you want to call a service within your Java code, this guide is the right place to get help. == Motivation -Various solutions already exist for calling services such as `RestTemplate` from spring or the JAX-RS client API. Further each and every service framework offers its own API as well. These solutions might be suitable for very small and simple projects (with one or two such invocations). However, with the trend of microservices the invocation of a service becomes a very common use-case that occurs all over the place. You typically need a solution that is very easy to use but supports flexible configuration, adding headers for authentication, mapping of errors from server, logging success/errors with duration for performance analysis, support for synchronous and asynchronous invocations, etc. This is exactly what this `devon4j` service-client solution brings for you. +Various solutions already exist for calling services, such as `RestTemplate` from spring or the JAX-RS client API. Furthermore, each and every service framework offers its own API as well. These solutions might be suitable for very small and simple projects (with one or two such invocations). However, with the trend of microservices, the invocation of a service becomes a very common use-case that occurs all over the place. You typically need a solution that is very easy to use but supports flexible configuration, adding headers for authentication, mapping of errors from the server, logging success/errors with duration for performance analysis, support for synchronous and asynchronous invocations, etc. This is exactly what this `devon4j` service-client solution brings to you. -== Dependency -You need to add (at least one of) these dependencies to your application: -[source,xml] --------- - - - com.devonfw.java.starters - devon4j-starter-http-client-rest-async - - - - com.devonfw.java.starters - devon4j-starter-http-client-rest-sync - - - - - - com.devonfw.java.starters - devon4j-starter-cxf-client-ws - --------- - -== Features -When invoking a service you need to consider many cross-cutting aspects. You might not think about them in the very first place and you do not want to implement them multiple times redundantly. Therefore you should consider using this approach. The following sub-sections list the covered features and aspects: - -=== Simple usage -Assuming you already have a Java interface `MyService` of the service you want to invoke: - -[source,java] --------- -package com.company.department.foo.mycomponent.service.api.rest; -... - -@Path("/myservice") -public interface MyService extends RestService { - - @POST - @Path("/getresult") - MyResult getResult(MyArgs myArgs); - - @DELETE - @Path("/entity/{id}") - void deleteEntity(@PathParam("id") long id); -} --------- - - -Then all you need to do is this: -[source,java] --------- -@Named -public class UcMyUseCaseImpl extends MyUseCaseBase implements UcMyUseCase { - @Inject - private ServiceClientFactory serviceClientFactory; - - ... - private void callSynchronous(MyArgs myArgs) { - MyService myService = this.serviceClientFactory.create(MyService.class); - // call of service over the wire, synchronously blocking until result is received or error occurred - MyResult myResult = myService.myMethod(myArgs); - handleResult(myResult); - } - - private void callAsynchronous(MyArgs myArgs) { - AsyncServiceClient client = this.serviceClientFactory.createAsync(MyService.class); - // call of service over the wire, will return when request is send and invoke handleResult asynchronously - client.call(client.get().myMethod(myArgs), this::handleResult); - } - - private void handleResult(MyResult myResult) { - ... - } - ... -} --------- - -As you can see both synchronous and asynchronous invocation of a service is very simple and type-safe. Still it is very flexible and powerful (see following features). The actual call of `myMethod` will technically call the remote service over the wire (e.g. via HTTP) including marshaling the arguments (e.g. converting `myArgs` to JSON) and unmarshalling the result (e.g. converting the received JSON to `myResult`). - -==== Asynchronous Invocation of void Methods - -If you want to call a service method with `void` as return type, the type-safe `call` method can not be used as `void` methods do not return a result. Therefore you can use the `callVoid` method as following: - -[source,java] --------- - private void callAsynchronousVoid(long id) { - AsyncServiceClient client = this.serviceClientFactory.createAsync(MyService.class); - // call of service over the wire, will return when request is send and invoke resultHandler asynchronously - Consumer resultHandler = r -> { System.out.println("Response received")}; - client.callVoid(() -> { client.get().deleteEntity(id);}, resultHandler); - } - --------- - -You may also provide `null` as `resultHandler` for "fire and forget". However, this will lead to the result being ignored so even in case of an error you will not be notified. - -=== Configuration -This solution allows a very flexible configuration on the following levels: - -1. Global configuration (defaults) -2. Configuration per remote service application (microservice) -3. Configuration per invocation. - -A configuration on a deeper level (e.g. 3) overrides the configuration from a higher level (e.g. 1). - -The configuration on Level 1 and 2 are configured via `application.properties` -(see link:guide-configuration.asciidoc[configuration guide]). -For Level 1 the prefix `service.client.default.` is used for properties. -Further, for level 2. the prefix `service.client.app.«application».` is used where `«application»` is the -technical name of the application providing the service. This name will automatically be derived from -the java package of the service interface (e.g. `foo` in `MyService` interface before) following our -link:coding-conventions.asciidoc#packages[packaging conventions]. -In case these conventions are not met it will fallback to the fully qualified name of the service interface. - -Configuration on Level 3 has to be provided as `Map` argument to the method -`ServiceClientFactory.create(Class serviceInterface, Map config)`. -The keys of this `Map` will not use prefixes (such as the ones above). For common configuration -parameters a type-safe builder is offered to create such map via `ServiceClientConfigBuilder`. -E.g. for testing you may want to do: -[source,java] --------- -this.serviceClientFactory.create(MyService.class, - new ServiceClientConfigBuilder().authBasic().userLogin(login).userPassword(password).buildMap()); --------- - -Here is an example of a configuration block for your `application.properties`: -``` -service.client.default.url=https://api.company.com/services/${type} -service.client.default.timeout.connection=120 -service.client.default.timeout.response=3600 - -service.client.app.bar.url=https://bar.company.com:8080/services/rest -service.client.app.bar.auth=basic -service.client.app.bar.user.login=user4711 -service.client.app.bar.user.password=ENC(jd5ZREpBqxuN9ok0IhnXabgw7V3EoG2p) - -service.client.app.foo.url=https://foo.company.com:8443/services/rest -# authForward: simply forward Authorization header (e.g. with JWT) to remote service -service.client.app.bar.auth=authForward -``` - -=== Service Discovery -You do not want to hardwire service URLs in your code, right? Therefore different strategies might apply -to _discover_ the URL of the invoked service. This is done internally by an implementation of the interface -`ServiceDiscoverer`. The default implementation simply reads the base URL from the configuration. -So you can simply add this to your `application.properties` as in the above configuration example. - -Assuming your service interface would have the fully qualified name -`com.company.department.foo.mycomponent.service.api.rest.MyService` then the URL would be resolved to -`https://foo.company.com:8443/services/rest` as the `«application»` is `foo`. - -Additionally, the URL might use the following variables that will automatically be resolved: - -* `${app}` to `«application»` (useful for default URL) -* `${type}` to the type of the service. E.g. `rest` in case of a link:guide-rest.asciidoc[REST] service and `ws` for a link:guide-soap.asciidoc[SOAP] service. -* `${local.server.port}` for the port of your current Java servlet container running the JVM. Should only used for testing with spring-boot random port mechanism (technically spring can not resolve this variable but we do it for you here). - -Therefore, the default URL may also be configured as: -``` -service.client.default.url=https://api.company.com/${app}/services/${type} -``` - -As you can use any implementation of `ServiceDiscoverer`, you can also easily use https://github.com/Netflix/eureka#eureka[eureka] (or anything else) instead to discover your services. -However, we recommend to use https://istio.io/[istio] instead as described below. - -=== Headers -A very common demand is to tweak (HTTP) headers in the request to invoke the service. May it be for security (authentication data) or for other cross-cutting concerns (such as the link:guide-logging.asciidoc#correlation-id[Correlation ID]). This is done internally by implementations of the interface `ServiceHeaderCustomizer`. -We already provide several implementations such as: - -* `ServiceHeaderCustomizerBasicAuth` for basic authentication (`auth=basic`). -* `ServiceHeaderCustomizerOAuth` for OAuth: passes a security token from security context such as a https://jwt.io/[JWT] via OAuth (`auth=oauth`). -* `ServiceHeaderCustomizerAuthForward` forwards the `Authorization` HTTP header from the running request to the request to the remote serivce as is (`auth=authForward`). Be careful to avoid security pitfals by misconfiguring this feature as it may also sensitive credentials (e.g. basic auth) to the remote service. Never use as default. -* `ServiceHeaderCustomizerCorrelationId` passed the link:guide-logging.asciidoc#correlation-id[Correlation ID] to the service request. - -Additionally, you can add further custom implementations of `ServiceHeaderCustomizer` for your individual requirements and additional headers. - -=== Timeouts -You can configure timeouts in a very flexible way. First of all you can configure timeouts to establish the connection (`timeout.connection`) and to wait for the response (`timeout.response`) separately. These timeouts can be configured on all three levels as described in the configuration section above. - -=== Error Handling -Whilst invoking a remote service an error may occur. This solution will automatically handle such errors and map them to a higher level `ServiceInvocationFailedException`. In general we separate two different types of errors: - -* *Network error* + -In such case (host not found, connection refused, time out, etc.) there is not even a response from the server. However, in advance to a low-level exception you will get a wrapped `ServiceInvocationFailedException` (with code `ServiceInvoke`) with a readable message containing the service that could not be invoked. -* *Service error* + -In case the service failed on the server-side the link:guide-rest.asciidoc#error-results[error result] will be parsed and thrown as a `ServiceInvocationFailedException` with the received message and code. - -This allows to catch and handle errors when a service-invocation failed. You can even distinguish business errors from the server-side from technical errors and implement retry strategies or the like. -Further the created exception contains detailed contextual information about the serivce that failed (service interface class, method, URL) what makes it much easier to trace down errors. Here is an example from our tests: - -``` -While invoking the service com.devonfw.test.app.myexample.service.api.rest.MyExampleRestService#businessError[http://localhost:50178/app/services/rest/my-example/v1/business-error] the following error occurred: Test of business error. Probably the service is temporary unavailable. Please try again later. If the problem persists contact your system administrator. -2f43b03e-685b-45c0-9aae-23ff4b220c85:BusinessErrorCode -``` - -You may even provide your own implementation of `ServiceClientErrorFactory` instead to provide an own exception class for this purpose. - -==== Handling Erros - -In case of a synchronous service invocation an error will be immediately thrown so you can sourround the call with a regular try-catch block: - -[source,java] --------- - private void callSynchronous(MyArgs myArgs) { - MyService myService = this.serviceClientFactory.create(MyService.class); - // call of service over the wire, synchronously blocking until result is received or error occurred - try { - MyResult myResult = myService.myMethod(myArgs); - handleResult(myResult); - } catch (ServiceInvocationFailedException e) { - if (e.isTechnical()) { - handleTechnicalError(e); - } else { - // error code you defined in the exception on the server side of the service - String errorCode = e.getCode(); - handleBusinessError(e, errorCode; - } - } catch (Throwable e) { // you may not handle this explicitly here... - handleTechnicalError(e); - } - } --------- - -If you are using asynchronous service invocation an error can occurr in a separate thread. Therefore you may and should define a custom error handler: - -[source,java] --------- - private void callAsynchronous(MyArgs myArgs) { - AsyncServiceClient client = this.serviceClientFactory.createAsync(MyService.class); - Consumer errorHandler = this::handleError; - client.setErrorHandler(errorHandler); - // call of service over the wire, will return when request is send and invoke handleResult asynchronously - client.call(client.get().myMethod(myArgs), this::handleResult); - } - - private void handleError(Throwalbe error) { - ... - } -} --------- - -The error handler consumes `Throwable` and not only `RuntimeException` so you can get notified even in case of an unexpected `OutOfMemoryError`, `NoClassDefFoundError`, or other technical problems. Please note that the error handler may also be called from the thread calling the service (e.g. if already creating the request fails). The default error handler used if no custom handler is set will only log the error and do nothing else. - -=== Logging -By default this solution will log all invocations including the URL of the invoked service, success or error status flag and the duration in seconds (with decimal nano precision as available). Therefore you can easily monitor the status and performance of the service invocations. Here is an example from our tests: -``` -Invoking service com.devonfw.test.app.myexample.service.api.rest.MyExampleRestService#greet[http://localhost:50178/app/services/rest/my-example/v1/greet/John%20Doe%20%26%20%3F%23] took PT20.309756622S (20309756622ns) and succeded with status 200. -``` +== Motivation -=== Resilience -Resilience adds a lot of complexity and that typically means that addressing this here would most probably result in not being up-to-date and not meeting all requirements. Therefore we recommend something completely different: the _sidecar_ approach (based on https://docs.microsoft.com/en-us/azure/architecture/patterns/sidecar[sidecar pattern]). This means that you use a generic proxy app that runs as a separate process on the same host, VM, or container of your actual application. Then in your app you are calling the service via the sidecar proxy on `localhost` (service discovery URL is e.g. `http://localhost:8081/${app}/services/${type}`) that then acts as proxy to the actual remote service. Now aspects such as resilience with circuit breaking and the actual service discovery can be configured in the sidecar proxy app and independent of your actual application. Therefore, you can even share and reuse configuration and experience with such a sidecar proxy app even across different technologies (Java, .NET/C#, Node.JS, etc.). Further, you do not pollute the technology stack of your actual app with the infrastructure for resilience, throttling, etc. and can update the app and the side-card independently when security-fixes are available. +*Spring* -Various implementations of such sidecar proxy apps are available as free open source software. -Our recommendation in devonfw is to use https://istio.io/[istio]. This not only provides such a side-car but also an entire management solution for service-mesh making administration and maintenance much easier. Platforms like OpenShift support this out of the box. +For Spring, follow the link:spring/guide-service-client-spring.asciidoc[Spring rest-client guide]. -However, if you are looking for details about side-car implementations for services you can have a look at the following links: +*Qaurkus* -* Netflix Sidecar - see http://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#_polyglot_support_with_sidecar[Spring Cloud Netflix docs] -* https://lyft.github.io/envoy/[Envoy] - see https://dzone.com/articles/microservices-patterns-with-envoy-sidecar-proxy-pa[Microservices Patterns With Envoy Sidecar Proxy] -* https://github.com/netflix/Prana[Prana] - see https://medium.com/netflix-techblog/prana-a-sidecar-for-your-netflix-paas-based-applications-and-services-258a5790a015[Prana: A Sidecar for your Netflix PaaS based Applications and Services] <- *Not updated as it's not used internally by Netflix* -* Keycloak - see http://www.hawkular.org/blog/2017/07/jaeger-with-security-proxy.html[Protecting Jaeger UI with a sidecar security proxy] \ No newline at end of file +For Quarkus, we recommend to follow the the official link:https://quarkus.io/guides/rest-client[Quarkus rest-client guide] \ No newline at end of file diff --git a/documentation/spring/guide-service-client-spring.asciidoc b/documentation/spring/guide-service-client-spring.asciidoc new file mode 100644 index 00000000..b645a47c --- /dev/null +++ b/documentation/spring/guide-service-client-spring.asciidoc @@ -0,0 +1,279 @@ +:toc: macro +:icons: font +toc::[] + += Service Client + +This guide is about consuming (calling) services from other applications (micro-services). For providing services, see the link:guide-service-layer.asciidoc[Service-Layer Guide]. Services can be consumed in the link:guide-client-layer.asciidoc[client] or the server. As the client is typically not written in Java, you should consult the according guide for your client technology. In case you want to call a service within your Java code, this guide is the right place to get help. + +== Motivation +Various solutions already exist for calling services, such as `RestTemplate` from spring or the JAX-RS client API. Furthermore, each and every service framework offers its own API as well. These solutions might be suitable for very small and simple projects (with one or two such invocations). However, with the trend of microservices, the invocation of a service becomes a very common use-case that occurs all over the place. You typically need a solution that is very easy to use but supports flexible configuration, adding headers for authentication, mapping of errors from the server, logging success/errors with duration for performance analysis, support for synchronous and asynchronous invocations, etc. This is exactly what this `devon4j` service-client solution brings to you. + +== Dependency +You need to add (at least one of) these dependencies to your application: +[source,xml] +-------- + + + com.devonfw.java.starters + devon4j-starter-http-client-rest-async + + + + com.devonfw.java.starters + devon4j-starter-http-client-rest-sync + + + + + + com.devonfw.java.starters + devon4j-starter-cxf-client-ws + +-------- + +== Features +When invoking a service you need to consider many cross-cutting aspects. You might not think about them in the very first place and you do not want to implement them multiple times redundantly. Therefore you should consider using this approach. The following sub-sections list the covered features and aspects: + +=== Simple usage +Assuming you already have a Java interface `MyService` of the service you want to invoke: + +[source,java] +-------- +package com.company.department.foo.mycomponent.service.api.rest; +... + +@Path("/myservice") +public interface MyService extends RestService { + + @POST + @Path("/getresult") + MyResult getResult(MyArgs myArgs); + + @DELETE + @Path("/entity/{id}") + void deleteEntity(@PathParam("id") long id); +} +-------- + + +Then all you need to do is this: +[source,java] +-------- +@Named +public class UcMyUseCaseImpl extends MyUseCaseBase implements UcMyUseCase { + @Inject + private ServiceClientFactory serviceClientFactory; + + ... + private void callSynchronous(MyArgs myArgs) { + MyService myService = this.serviceClientFactory.create(MyService.class); + // call of service over the wire, synchronously blocking until result is received or error occurred + MyResult myResult = myService.myMethod(myArgs); + handleResult(myResult); + } + + private void callAsynchronous(MyArgs myArgs) { + AsyncServiceClient client = this.serviceClientFactory.createAsync(MyService.class); + // call of service over the wire, will return when request is send and invoke handleResult asynchronously + client.call(client.get().myMethod(myArgs), this::handleResult); + } + + private void handleResult(MyResult myResult) { + ... + } + ... +} +-------- + +As you can see both synchronous and asynchronous invocation of a service is very simple and type-safe. Still it is very flexible and powerful (see following features). The actual call of `myMethod` will technically call the remote service over the wire (e.g. via HTTP) including marshaling the arguments (e.g. converting `myArgs` to JSON) and unmarshalling the result (e.g. converting the received JSON to `myResult`). + +==== Asynchronous Invocation of void Methods + +If you want to call a service method with `void` as return type, the type-safe `call` method can not be used as `void` methods do not return a result. Therefore you can use the `callVoid` method as following: + +[source,java] +-------- + private void callAsynchronousVoid(long id) { + AsyncServiceClient client = this.serviceClientFactory.createAsync(MyService.class); + // call of service over the wire, will return when request is send and invoke resultHandler asynchronously + Consumer resultHandler = r -> { System.out.println("Response received")}; + client.callVoid(() -> { client.get().deleteEntity(id);}, resultHandler); + } + +-------- + +You may also provide `null` as `resultHandler` for "fire and forget". However, this will lead to the result being ignored so even in case of an error you will not be notified. + +=== Configuration +This solution allows a very flexible configuration on the following levels: + +1. Global configuration (defaults) +2. Configuration per remote service application (microservice) +3. Configuration per invocation. + +A configuration on a deeper level (e.g. 3) overrides the configuration from a higher level (e.g. 1). + +The configuration on Level 1 and 2 are configured via `application.properties` +(see link:guide-configuration.asciidoc[configuration guide]). +For Level 1 the prefix `service.client.default.` is used for properties. +Further, for level 2. the prefix `service.client.app.«application».` is used where `«application»` is the +technical name of the application providing the service. This name will automatically be derived from +the java package of the service interface (e.g. `foo` in `MyService` interface before) following our +link:coding-conventions.asciidoc#packages[packaging conventions]. +In case these conventions are not met it will fallback to the fully qualified name of the service interface. + +Configuration on Level 3 has to be provided as `Map` argument to the method +`ServiceClientFactory.create(Class serviceInterface, Map config)`. +The keys of this `Map` will not use prefixes (such as the ones above). For common configuration +parameters a type-safe builder is offered to create such map via `ServiceClientConfigBuilder`. +E.g. for testing you may want to do: +[source,java] +-------- +this.serviceClientFactory.create(MyService.class, + new ServiceClientConfigBuilder().authBasic().userLogin(login).userPassword(password).buildMap()); +-------- + +Here is an example of a configuration block for your `application.properties`: +``` +service.client.default.url=https://api.company.com/services/${type} +service.client.default.timeout.connection=120 +service.client.default.timeout.response=3600 + +service.client.app.bar.url=https://bar.company.com:8080/services/rest +service.client.app.bar.auth=basic +service.client.app.bar.user.login=user4711 +service.client.app.bar.user.password=ENC(jd5ZREpBqxuN9ok0IhnXabgw7V3EoG2p) + +service.client.app.foo.url=https://foo.company.com:8443/services/rest +# authForward: simply forward Authorization header (e.g. with JWT) to remote service +service.client.app.bar.auth=authForward +``` + +=== Service Discovery +You do not want to hardwire service URLs in your code, right? Therefore different strategies might apply +to _discover_ the URL of the invoked service. This is done internally by an implementation of the interface +`ServiceDiscoverer`. The default implementation simply reads the base URL from the configuration. +So you can simply add this to your `application.properties` as in the above configuration example. + +Assuming your service interface would have the fully qualified name +`com.company.department.foo.mycomponent.service.api.rest.MyService` then the URL would be resolved to +`https://foo.company.com:8443/services/rest` as the `«application»` is `foo`. + +Additionally, the URL might use the following variables that will automatically be resolved: + +* `${app}` to `«application»` (useful for default URL) +* `${type}` to the type of the service. E.g. `rest` in case of a link:guide-rest.asciidoc[REST] service and `ws` for a link:guide-soap.asciidoc[SOAP] service. +* `${local.server.port}` for the port of your current Java servlet container running the JVM. Should only used for testing with spring-boot random port mechanism (technically spring can not resolve this variable but we do it for you here). + +Therefore, the default URL may also be configured as: +``` +service.client.default.url=https://api.company.com/${app}/services/${type} +``` + +As you can use any implementation of `ServiceDiscoverer`, you can also easily use https://github.com/Netflix/eureka#eureka[eureka] (or anything else) instead to discover your services. +However, we recommend to use https://istio.io/[istio] instead as described below. + +=== Headers +A very common demand is to tweak (HTTP) headers in the request to invoke the service. May it be for security (authentication data) or for other cross-cutting concerns (such as the link:guide-logging.asciidoc#correlation-id[Correlation ID]). This is done internally by implementations of the interface `ServiceHeaderCustomizer`. +We already provide several implementations such as: + +* `ServiceHeaderCustomizerBasicAuth` for basic authentication (`auth=basic`). +* `ServiceHeaderCustomizerOAuth` for OAuth: passes a security token from security context such as a https://jwt.io/[JWT] via OAuth (`auth=oauth`). +* `ServiceHeaderCustomizerAuthForward` forwards the `Authorization` HTTP header from the running request to the request to the remote serivce as is (`auth=authForward`). Be careful to avoid security pitfals by misconfiguring this feature as it may also sensitive credentials (e.g. basic auth) to the remote service. Never use as default. +* `ServiceHeaderCustomizerCorrelationId` passed the link:guide-logging.asciidoc#correlation-id[Correlation ID] to the service request. + +Additionally, you can add further custom implementations of `ServiceHeaderCustomizer` for your individual requirements and additional headers. + +=== Timeouts +You can configure timeouts in a very flexible way. First of all you can configure timeouts to establish the connection (`timeout.connection`) and to wait for the response (`timeout.response`) separately. These timeouts can be configured on all three levels as described in the configuration section above. + +=== Error Handling +Whilst invoking a remote service an error may occur. This solution will automatically handle such errors and map them to a higher level `ServiceInvocationFailedException`. In general we separate two different types of errors: + +* *Network error* + +In such case (host not found, connection refused, time out, etc.) there is not even a response from the server. However, in advance to a low-level exception you will get a wrapped `ServiceInvocationFailedException` (with code `ServiceInvoke`) with a readable message containing the service that could not be invoked. +* *Service error* + +In case the service failed on the server-side the link:guide-rest.asciidoc#error-results[error result] will be parsed and thrown as a `ServiceInvocationFailedException` with the received message and code. + +This allows to catch and handle errors when a service-invocation failed. You can even distinguish business errors from the server-side from technical errors and implement retry strategies or the like. +Further the created exception contains detailed contextual information about the serivce that failed (service interface class, method, URL) what makes it much easier to trace down errors. Here is an example from our tests: + +``` +While invoking the service com.devonfw.test.app.myexample.service.api.rest.MyExampleRestService#businessError[http://localhost:50178/app/services/rest/my-example/v1/business-error] the following error occurred: Test of business error. Probably the service is temporary unavailable. Please try again later. If the problem persists contact your system administrator. +2f43b03e-685b-45c0-9aae-23ff4b220c85:BusinessErrorCode +``` + +You may even provide your own implementation of `ServiceClientErrorFactory` instead to provide an own exception class for this purpose. + +==== Handling Erros + +In case of a synchronous service invocation an error will be immediately thrown so you can sourround the call with a regular try-catch block: + +[source,java] +-------- + private void callSynchronous(MyArgs myArgs) { + MyService myService = this.serviceClientFactory.create(MyService.class); + // call of service over the wire, synchronously blocking until result is received or error occurred + try { + MyResult myResult = myService.myMethod(myArgs); + handleResult(myResult); + } catch (ServiceInvocationFailedException e) { + if (e.isTechnical()) { + handleTechnicalError(e); + } else { + // error code you defined in the exception on the server side of the service + String errorCode = e.getCode(); + handleBusinessError(e, errorCode; + } + } catch (Throwable e) { // you may not handle this explicitly here... + handleTechnicalError(e); + } + } +-------- + +If you are using asynchronous service invocation an error can occurr in a separate thread. Therefore you may and should define a custom error handler: + +[source,java] +-------- + private void callAsynchronous(MyArgs myArgs) { + AsyncServiceClient client = this.serviceClientFactory.createAsync(MyService.class); + Consumer errorHandler = this::handleError; + client.setErrorHandler(errorHandler); + // call of service over the wire, will return when request is send and invoke handleResult asynchronously + client.call(client.get().myMethod(myArgs), this::handleResult); + } + + private void handleError(Throwalbe error) { + ... + } +} +-------- + +The error handler consumes `Throwable` and not only `RuntimeException` so you can get notified even in case of an unexpected `OutOfMemoryError`, `NoClassDefFoundError`, or other technical problems. Please note that the error handler may also be called from the thread calling the service (e.g. if already creating the request fails). The default error handler used if no custom handler is set will only log the error and do nothing else. + +=== Logging +By default this solution will log all invocations including the URL of the invoked service, success or error status flag and the duration in seconds (with decimal nano precision as available). Therefore you can easily monitor the status and performance of the service invocations. Here is an example from our tests: +``` +Invoking service com.devonfw.test.app.myexample.service.api.rest.MyExampleRestService#greet[http://localhost:50178/app/services/rest/my-example/v1/greet/John%20Doe%20%26%20%3F%23] took PT20.309756622S (20309756622ns) and succeded with status 200. +``` + +=== Resilience +Resilience adds a lot of complexity and that typically means that addressing this here would most probably result in not being up-to-date and not meeting all requirements. Therefore we recommend something completely different: the _sidecar_ approach (based on https://docs.microsoft.com/en-us/azure/architecture/patterns/sidecar[sidecar pattern]). This means that you use a generic proxy app that runs as a separate process on the same host, VM, or container of your actual application. Then in your app you are calling the service via the sidecar proxy on `localhost` (service discovery URL is e.g. `http://localhost:8081/${app}/services/${type}`) that then acts as proxy to the actual remote service. Now aspects such as resilience with circuit breaking and the actual service discovery can be configured in the sidecar proxy app and independent of your actual application. Therefore, you can even share and reuse configuration and experience with such a sidecar proxy app even across different technologies (Java, .NET/C#, Node.JS, etc.). Further, you do not pollute the technology stack of your actual app with the infrastructure for resilience, throttling, etc. and can update the app and the side-card independently when security-fixes are available. + +Various implementations of such sidecar proxy apps are available as free open source software. +Our recommendation in devonfw is to use https://istio.io/[istio]. This not only provides such a side-car but also an entire management solution for service-mesh making administration and maintenance much easier. Platforms like OpenShift support this out of the box. + +However, if you are looking for details about side-car implementations for services you can have a look at the following links: + +* Netflix Sidecar - see http://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#_polyglot_support_with_sidecar[Spring Cloud Netflix docs] +* https://lyft.github.io/envoy/[Envoy] - see https://dzone.com/articles/microservices-patterns-with-envoy-sidecar-proxy-pa[Microservices Patterns With Envoy Sidecar Proxy] +* https://github.com/netflix/Prana[Prana] - see https://medium.com/netflix-techblog/prana-a-sidecar-for-your-netflix-paas-based-applications-and-services-258a5790a015[Prana: A Sidecar for your Netflix PaaS based Applications and Services] <- *Not updated as it's not used internally by Netflix* +* Keycloak - see http://www.hawkular.org/blog/2017/07/jaeger-with-security-proxy.html[Protecting Jaeger UI with a sidecar security proxy] \ No newline at end of file From 3ecb71fa521d7b7bd9b201051e97076e05c227fd Mon Sep 17 00:00:00 2001 From: Vicaria Barker Date: Wed, 1 Dec 2021 11:51:17 +0100 Subject: [PATCH 2/9] Corrected mistakes in decisision-services-framework and guide-service-client, and uploaded the correct guide-service-client-spring with fixed links --- .../decision-service-framework.asciidoc | 6 +++--- documentation/guide-service-client.asciidoc | 4 ++-- .../guide-service-client-spring.asciidoc | 19 ++++++++----------- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/documentation/decision-service-framework.asciidoc b/documentation/decision-service-framework.asciidoc index a12ebfd1..e8582820 100644 --- a/documentation/decision-service-framework.asciidoc +++ b/documentation/decision-service-framework.asciidoc @@ -6,9 +6,9 @@ toc::[] We need to choose which framework(s) we want to use for building services. For the devonfw, we focus on a standard API, if available. However, we also want to recommend an implementation. While projects would still be able to choose whatever they want, we want to suggest the best, most robust, and established solution. This way, projects do not have to worry about the decision and can rely on a production-ready framework without running into any trouble. Also, besides the standard, the configuration of the implementation framework differs, so we want to give instructions in the documentation and by our sample application. This is why, in the end, the implementation also matters. If a project has a customer demand to use something else, the project has to take care of it. We will always suggest and "support" ONE solution. == REST Services -For REST services, devonfw relies on the JAX-RS standard (and NOT on spring-mvc with its proprietary annotations). https://github.com/jax-rs[JAX-RS] (Java API for RESTful Web Services) is a Java programming language API to develop web services following the Representational State Transfer (REST) architectural pattern. -For Apache CXF, the spring container was the first choice, but container abstraction has been properly introduced by design, so it can be used in JEE application servers. Apache CXF is a services framework that helps to build and develop services using frontend programming APIs, such as JAX-RS (https://cxf.apache.org/). Everything works smoothly in our sample application, and in addition, we collected feedback from various projects utilizing CXF, either with XML or JSON, with reported success in production. Therefore, we decided to use Apache CXF (here | for Spring). -For Quarkus applications, Devon4j recommends to use RESTEasy. RESTEasy is a JAX-RS implementation aimed at providing productivity frameworks for developing client and server RESTful applications and services in Java (https://github.com/resteasy/resteasy) +For REST services, devonfw relies on the JAX-RS standard (and NOT on spring-mvc with its proprietary annotations). https://https://github.com/eclipse-ee4j/jaxrs-api[JAX-RS] (Jakarta RESTful Web Services) is a Java programming language API to develop web services following the Representational State Transfer (REST) architectural pattern. +For https://cxf.apache.org/[Apache CXF], the spring container was the first choice, but container abstraction has been properly introduced by design, so it can be used in JEE application servers. Apache CXF is a services framework that helps to build and develop services using frontend programming APIs, such as JAX-RS. Everything works smoothly in our sample application, and in addition, we collected feedback from various projects utilizing CXF, either with XML or JSON, with reported success in production. Therefore, we decided to use Apache CXF for Spring. +For Quarkus applications, devon4j recommends to use RESTEasy. RESTEasy is a JAX-RS implementation aimed at providing productivity frameworks for developing client and server RESTful applications and services in Java (https://github.com/resteasy/resteasy) == WebServices diff --git a/documentation/guide-service-client.asciidoc b/documentation/guide-service-client.asciidoc index d5126a57..403e459a 100644 --- a/documentation/guide-service-client.asciidoc +++ b/documentation/guide-service-client.asciidoc @@ -9,12 +9,12 @@ This guide is about consuming (calling) services from other applications (micro- == Motivation Various solutions already exist for calling services, such as `RestTemplate` from spring or the JAX-RS client API. Furthermore, each and every service framework offers its own API as well. These solutions might be suitable for very small and simple projects (with one or two such invocations). However, with the trend of microservices, the invocation of a service becomes a very common use-case that occurs all over the place. You typically need a solution that is very easy to use but supports flexible configuration, adding headers for authentication, mapping of errors from the server, logging success/errors with duration for performance analysis, support for synchronous and asynchronous invocations, etc. This is exactly what this `devon4j` service-client solution brings to you. -== Motivation +== Usage *Spring* For Spring, follow the link:spring/guide-service-client-spring.asciidoc[Spring rest-client guide]. -*Qaurkus* +*Quarkus* For Quarkus, we recommend to follow the the official link:https://quarkus.io/guides/rest-client[Quarkus rest-client guide] \ No newline at end of file diff --git a/documentation/spring/guide-service-client-spring.asciidoc b/documentation/spring/guide-service-client-spring.asciidoc index b645a47c..405bdfb0 100644 --- a/documentation/spring/guide-service-client-spring.asciidoc +++ b/documentation/spring/guide-service-client-spring.asciidoc @@ -2,12 +2,9 @@ :icons: font toc::[] -= Service Client += Service Client in devon4j-spring -This guide is about consuming (calling) services from other applications (micro-services). For providing services, see the link:guide-service-layer.asciidoc[Service-Layer Guide]. Services can be consumed in the link:guide-client-layer.asciidoc[client] or the server. As the client is typically not written in Java, you should consult the according guide for your client technology. In case you want to call a service within your Java code, this guide is the right place to get help. - -== Motivation -Various solutions already exist for calling services, such as `RestTemplate` from spring or the JAX-RS client API. Furthermore, each and every service framework offers its own API as well. These solutions might be suitable for very small and simple projects (with one or two such invocations). However, with the trend of microservices, the invocation of a service becomes a very common use-case that occurs all over the place. You typically need a solution that is very easy to use but supports flexible configuration, adding headers for authentication, mapping of errors from the server, logging success/errors with duration for performance analysis, support for synchronous and asynchronous invocations, etc. This is exactly what this `devon4j` service-client solution brings to you. +This guide is about consuming (calling) services from other applications (micro-services) in devon4j-spring. == Dependency You need to add (at least one of) these dependencies to your application: @@ -122,12 +119,12 @@ This solution allows a very flexible configuration on the following levels: A configuration on a deeper level (e.g. 3) overrides the configuration from a higher level (e.g. 1). The configuration on Level 1 and 2 are configured via `application.properties` -(see link:guide-configuration.asciidoc[configuration guide]). +(see link:../guide-configuration.asciidoc[configuration guide]). For Level 1 the prefix `service.client.default.` is used for properties. Further, for level 2. the prefix `service.client.app.«application».` is used where `«application»` is the technical name of the application providing the service. This name will automatically be derived from the java package of the service interface (e.g. `foo` in `MyService` interface before) following our -link:coding-conventions.asciidoc#packages[packaging conventions]. +link:../coding-conventions.asciidoc#packages[packaging conventions]. In case these conventions are not met it will fallback to the fully qualified name of the service interface. Configuration on Level 3 has to be provided as `Map` argument to the method @@ -170,7 +167,7 @@ Assuming your service interface would have the fully qualified name Additionally, the URL might use the following variables that will automatically be resolved: * `${app}` to `«application»` (useful for default URL) -* `${type}` to the type of the service. E.g. `rest` in case of a link:guide-rest.asciidoc[REST] service and `ws` for a link:guide-soap.asciidoc[SOAP] service. +* `${type}` to the type of the service. E.g. `rest` in case of a link:../guide-rest.asciidoc[REST] service and `ws` for a link:../guide-soap.asciidoc[SOAP] service. * `${local.server.port}` for the port of your current Java servlet container running the JVM. Should only used for testing with spring-boot random port mechanism (technically spring can not resolve this variable but we do it for you here). Therefore, the default URL may also be configured as: @@ -182,13 +179,13 @@ As you can use any implementation of `ServiceDiscoverer`, you can also easily us However, we recommend to use https://istio.io/[istio] instead as described below. === Headers -A very common demand is to tweak (HTTP) headers in the request to invoke the service. May it be for security (authentication data) or for other cross-cutting concerns (such as the link:guide-logging.asciidoc#correlation-id[Correlation ID]). This is done internally by implementations of the interface `ServiceHeaderCustomizer`. +A very common demand is to tweak (HTTP) headers in the request to invoke the service. May it be for security (authentication data) or for other cross-cutting concerns (such as the link:../guide-logging.asciidoc#correlation-id[Correlation ID]). This is done internally by implementations of the interface `ServiceHeaderCustomizer`. We already provide several implementations such as: * `ServiceHeaderCustomizerBasicAuth` for basic authentication (`auth=basic`). * `ServiceHeaderCustomizerOAuth` for OAuth: passes a security token from security context such as a https://jwt.io/[JWT] via OAuth (`auth=oauth`). * `ServiceHeaderCustomizerAuthForward` forwards the `Authorization` HTTP header from the running request to the request to the remote serivce as is (`auth=authForward`). Be careful to avoid security pitfals by misconfiguring this feature as it may also sensitive credentials (e.g. basic auth) to the remote service. Never use as default. -* `ServiceHeaderCustomizerCorrelationId` passed the link:guide-logging.asciidoc#correlation-id[Correlation ID] to the service request. +* `ServiceHeaderCustomizerCorrelationId` passed the link:../guide-logging.asciidoc#correlation-id[Correlation ID] to the service request. Additionally, you can add further custom implementations of `ServiceHeaderCustomizer` for your individual requirements and additional headers. @@ -201,7 +198,7 @@ Whilst invoking a remote service an error may occur. This solution will automati * *Network error* + In such case (host not found, connection refused, time out, etc.) there is not even a response from the server. However, in advance to a low-level exception you will get a wrapped `ServiceInvocationFailedException` (with code `ServiceInvoke`) with a readable message containing the service that could not be invoked. * *Service error* + -In case the service failed on the server-side the link:guide-rest.asciidoc#error-results[error result] will be parsed and thrown as a `ServiceInvocationFailedException` with the received message and code. +In case the service failed on the server-side the link:../guide-rest.asciidoc#error-results[error result] will be parsed and thrown as a `ServiceInvocationFailedException` with the received message and code. This allows to catch and handle errors when a service-invocation failed. You can even distinguish business errors from the server-side from technical errors and implement retry strategies or the like. Further the created exception contains detailed contextual information about the serivce that failed (service interface class, method, URL) what makes it much easier to trace down errors. Here is an example from our tests: From 9d609676af35931c550777a8d14da96bd050bd9b Mon Sep 17 00:00:00 2001 From: Vicaria Barker Date: Wed, 1 Dec 2021 12:06:38 +0100 Subject: [PATCH 3/9] Removed duplicate https:// --- documentation/decision-service-framework.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/decision-service-framework.asciidoc b/documentation/decision-service-framework.asciidoc index e8582820..5814743b 100644 --- a/documentation/decision-service-framework.asciidoc +++ b/documentation/decision-service-framework.asciidoc @@ -6,7 +6,7 @@ toc::[] We need to choose which framework(s) we want to use for building services. For the devonfw, we focus on a standard API, if available. However, we also want to recommend an implementation. While projects would still be able to choose whatever they want, we want to suggest the best, most robust, and established solution. This way, projects do not have to worry about the decision and can rely on a production-ready framework without running into any trouble. Also, besides the standard, the configuration of the implementation framework differs, so we want to give instructions in the documentation and by our sample application. This is why, in the end, the implementation also matters. If a project has a customer demand to use something else, the project has to take care of it. We will always suggest and "support" ONE solution. == REST Services -For REST services, devonfw relies on the JAX-RS standard (and NOT on spring-mvc with its proprietary annotations). https://https://github.com/eclipse-ee4j/jaxrs-api[JAX-RS] (Jakarta RESTful Web Services) is a Java programming language API to develop web services following the Representational State Transfer (REST) architectural pattern. +For REST services, devonfw relies on the JAX-RS standard (and NOT on spring-mvc with its proprietary annotations). https://github.com/eclipse-ee4j/jaxrs-api[JAX-RS] (Jakarta RESTful Web Services) is a Java programming language API to develop web services following the Representational State Transfer (REST) architectural pattern. For https://cxf.apache.org/[Apache CXF], the spring container was the first choice, but container abstraction has been properly introduced by design, so it can be used in JEE application servers. Apache CXF is a services framework that helps to build and develop services using frontend programming APIs, such as JAX-RS. Everything works smoothly in our sample application, and in addition, we collected feedback from various projects utilizing CXF, either with XML or JSON, with reported success in production. Therefore, we decided to use Apache CXF for Spring. For Quarkus applications, devon4j recommends to use RESTEasy. RESTEasy is a JAX-RS implementation aimed at providing productivity frameworks for developing client and server RESTful applications and services in Java (https://github.com/resteasy/resteasy) From b08f537fb821e468edec1f571d3407651ee5e671 Mon Sep 17 00:00:00 2001 From: Vicaria Barker Date: Wed, 1 Dec 2021 15:19:41 +0100 Subject: [PATCH 4/9] Fixed the RESTEasy link in decision-service-framework, a typo in guide-service-client, and common english mistakes (mostly commas) in guide-service-client-spring --- .../decision-service-framework.asciidoc | 2 +- documentation/guide-service-client.asciidoc | 2 +- .../guide-service-client-spring.asciidoc | 64 +++++++++---------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/documentation/decision-service-framework.asciidoc b/documentation/decision-service-framework.asciidoc index 5814743b..237e7ca7 100644 --- a/documentation/decision-service-framework.asciidoc +++ b/documentation/decision-service-framework.asciidoc @@ -8,7 +8,7 @@ We need to choose which framework(s) we want to use for building services. For t == REST Services For REST services, devonfw relies on the JAX-RS standard (and NOT on spring-mvc with its proprietary annotations). https://github.com/eclipse-ee4j/jaxrs-api[JAX-RS] (Jakarta RESTful Web Services) is a Java programming language API to develop web services following the Representational State Transfer (REST) architectural pattern. For https://cxf.apache.org/[Apache CXF], the spring container was the first choice, but container abstraction has been properly introduced by design, so it can be used in JEE application servers. Apache CXF is a services framework that helps to build and develop services using frontend programming APIs, such as JAX-RS. Everything works smoothly in our sample application, and in addition, we collected feedback from various projects utilizing CXF, either with XML or JSON, with reported success in production. Therefore, we decided to use Apache CXF for Spring. -For Quarkus applications, devon4j recommends to use RESTEasy. RESTEasy is a JAX-RS implementation aimed at providing productivity frameworks for developing client and server RESTful applications and services in Java (https://github.com/resteasy/resteasy) +For Quarkus applications, devon4j recommends to use https://github.com/resteasy/resteasy[RESTEasy], which is a JAX-RS implementation aimed at providing productivity frameworks for developing client and server RESTful applications and services in Java. == WebServices diff --git a/documentation/guide-service-client.asciidoc b/documentation/guide-service-client.asciidoc index 403e459a..0b549b4c 100644 --- a/documentation/guide-service-client.asciidoc +++ b/documentation/guide-service-client.asciidoc @@ -4,7 +4,7 @@ toc::[] = Service Client -This guide is about consuming (calling) services from other applications (micro-services). For providing services, see the link:guide-service-layer.asciidoc[Service-Layer Guide]. Services can be consumed in the link:guide-client-layer.asciidoc[client] or the server. As the client is typically not written in Java, you should consult the according guide for your client technology. In case you want to call a service within your Java code, this guide is the right place to get help. +This guide is about consuming (calling) services from other applications (micro-services). For providing services, see the link:guide-service-layer.asciidoc[Service-Layer Guide]. Services can be consumed by the link:guide-client-layer.asciidoc[client] or the server. As the client is typically not written in Java, you should consult the according guide for your client technology. In case you want to call a service within your Java code, this guide is the right place to get help. == Motivation Various solutions already exist for calling services, such as `RestTemplate` from spring or the JAX-RS client API. Furthermore, each and every service framework offers its own API as well. These solutions might be suitable for very small and simple projects (with one or two such invocations). However, with the trend of microservices, the invocation of a service becomes a very common use-case that occurs all over the place. You typically need a solution that is very easy to use but supports flexible configuration, adding headers for authentication, mapping of errors from the server, logging success/errors with duration for performance analysis, support for synchronous and asynchronous invocations, etc. This is exactly what this `devon4j` service-client solution brings to you. diff --git a/documentation/spring/guide-service-client-spring.asciidoc b/documentation/spring/guide-service-client-spring.asciidoc index 405bdfb0..827dd039 100644 --- a/documentation/spring/guide-service-client-spring.asciidoc +++ b/documentation/spring/guide-service-client-spring.asciidoc @@ -37,7 +37,7 @@ You need to add (at least one of) these dependencies to your application: -------- == Features -When invoking a service you need to consider many cross-cutting aspects. You might not think about them in the very first place and you do not want to implement them multiple times redundantly. Therefore you should consider using this approach. The following sub-sections list the covered features and aspects: +When invoking a service, you need to consider many cross-cutting aspects. You might not think about them in the very first place and you do not want to redundantly implement them multiple times. Therefore, you should consider using this approach. The following sub-sections list the covered features and aspects: === Simple usage Assuming you already have a Java interface `MyService` of the service you want to invoke: @@ -61,7 +61,7 @@ public interface MyService extends RestService { -------- -Then all you need to do is this: +Then, all you need to do is this: [source,java] -------- @Named @@ -90,11 +90,11 @@ public class UcMyUseCaseImpl extends MyUseCaseBase implements UcMyUseCase { } -------- -As you can see both synchronous and asynchronous invocation of a service is very simple and type-safe. Still it is very flexible and powerful (see following features). The actual call of `myMethod` will technically call the remote service over the wire (e.g. via HTTP) including marshaling the arguments (e.g. converting `myArgs` to JSON) and unmarshalling the result (e.g. converting the received JSON to `myResult`). +As you can see, both synchronous and asynchronous invocation of a service is very simple and type-safe. However, it is also very flexible and powerful (see following features). The actual call of `myMethod` will technically call the remote service over the wire (e.g. via HTTP), including marshalling the arguments (e.g. converting `myArgs` to JSON) and unmarshalling the result (e.g. converting the received JSON to `myResult`). ==== Asynchronous Invocation of void Methods -If you want to call a service method with `void` as return type, the type-safe `call` method can not be used as `void` methods do not return a result. Therefore you can use the `callVoid` method as following: +If you want to call a service method with `void` as the return type, the type-safe `call` method cannot be used as `void` methods do not return a result. Therefore you can use the `callVoid` method as following: [source,java] -------- @@ -107,7 +107,7 @@ If you want to call a service method with `void` as return type, the type-safe ` -------- -You may also provide `null` as `resultHandler` for "fire and forget". However, this will lead to the result being ignored so even in case of an error you will not be notified. +You may also provide `null` as `resultHandler` for "fire and forget". However, this will lead to the result being ignored, so even in the case of an error you will not be notified. === Configuration This solution allows a very flexible configuration on the following levels: @@ -120,18 +120,18 @@ A configuration on a deeper level (e.g. 3) overrides the configuration from a hi The configuration on Level 1 and 2 are configured via `application.properties` (see link:../guide-configuration.asciidoc[configuration guide]). -For Level 1 the prefix `service.client.default.` is used for properties. -Further, for level 2. the prefix `service.client.app.«application».` is used where `«application»` is the +For Level 1, the prefix `service.client.default.` is used for properties. +Further, for level 2, the prefix `service.client.app.«application».` is used where `«application»` is the technical name of the application providing the service. This name will automatically be derived from the java package of the service interface (e.g. `foo` in `MyService` interface before) following our link:../coding-conventions.asciidoc#packages[packaging conventions]. -In case these conventions are not met it will fallback to the fully qualified name of the service interface. +In case these conventions are not met, it will fall back to the fully qualified name of the service interface. -Configuration on Level 3 has to be provided as `Map` argument to the method +Configuration on Level 3 has to be provided as a `Map` argument to the method `ServiceClientFactory.create(Class serviceInterface, Map config)`. The keys of this `Map` will not use prefixes (such as the ones above). For common configuration -parameters a type-safe builder is offered to create such map via `ServiceClientConfigBuilder`. -E.g. for testing you may want to do: +parameters, a type-safe builder is offered to create such a map via `ServiceClientConfigBuilder`. +E.g. for testing, you may want to do: [source,java] -------- this.serviceClientFactory.create(MyService.class, @@ -155,20 +155,20 @@ service.client.app.bar.auth=authForward ``` === Service Discovery -You do not want to hardwire service URLs in your code, right? Therefore different strategies might apply +You do not want to hardwire service URLs in your code, right? Therefore, different strategies might apply to _discover_ the URL of the invoked service. This is done internally by an implementation of the interface `ServiceDiscoverer`. The default implementation simply reads the base URL from the configuration. -So you can simply add this to your `application.properties` as in the above configuration example. +You can simply add this to your `application.properties` as in the above configuration example. -Assuming your service interface would have the fully qualified name -`com.company.department.foo.mycomponent.service.api.rest.MyService` then the URL would be resolved to -`https://foo.company.com:8443/services/rest` as the `«application»` is `foo`. +Assuming your service interface has the fully qualified name +`com.company.department.foo.mycomponent.service.api.rest.MyService`, then the URL would be resolved to +`https://foo.company.com:8443/services/rest`, as the `«application»` is `foo`. Additionally, the URL might use the following variables that will automatically be resolved: * `${app}` to `«application»` (useful for default URL) * `${type}` to the type of the service. E.g. `rest` in case of a link:../guide-rest.asciidoc[REST] service and `ws` for a link:../guide-soap.asciidoc[SOAP] service. -* `${local.server.port}` for the port of your current Java servlet container running the JVM. Should only used for testing with spring-boot random port mechanism (technically spring can not resolve this variable but we do it for you here). +* `${local.server.port}` for the port of your current Java servlet container running the JVM. Should only be used for testing with spring-boot random port mechanism (technically spring cannot resolve this variable, but we do it for you here). Therefore, the default URL may also be configured as: ``` @@ -176,7 +176,7 @@ service.client.default.url=https://api.company.com/${app}/services/${type} ``` As you can use any implementation of `ServiceDiscoverer`, you can also easily use https://github.com/Netflix/eureka#eureka[eureka] (or anything else) instead to discover your services. -However, we recommend to use https://istio.io/[istio] instead as described below. +However, we recommend to use https://istio.io/[istio] instead, as described below. === Headers A very common demand is to tweak (HTTP) headers in the request to invoke the service. May it be for security (authentication data) or for other cross-cutting concerns (such as the link:../guide-logging.asciidoc#correlation-id[Correlation ID]). This is done internally by implementations of the interface `ServiceHeaderCustomizer`. @@ -184,24 +184,24 @@ We already provide several implementations such as: * `ServiceHeaderCustomizerBasicAuth` for basic authentication (`auth=basic`). * `ServiceHeaderCustomizerOAuth` for OAuth: passes a security token from security context such as a https://jwt.io/[JWT] via OAuth (`auth=oauth`). -* `ServiceHeaderCustomizerAuthForward` forwards the `Authorization` HTTP header from the running request to the request to the remote serivce as is (`auth=authForward`). Be careful to avoid security pitfals by misconfiguring this feature as it may also sensitive credentials (e.g. basic auth) to the remote service. Never use as default. +* `ServiceHeaderCustomizerAuthForward` forwards the `Authorization` HTTP header from the running request to the request to the remote service as is (`auth=authForward`). Be careful to avoid security pitfalls by misconfiguring this feature, as it may also contain sensitive credentials (e.g. basic auth) to the remote service. Never use as default. * `ServiceHeaderCustomizerCorrelationId` passed the link:../guide-logging.asciidoc#correlation-id[Correlation ID] to the service request. Additionally, you can add further custom implementations of `ServiceHeaderCustomizer` for your individual requirements and additional headers. === Timeouts -You can configure timeouts in a very flexible way. First of all you can configure timeouts to establish the connection (`timeout.connection`) and to wait for the response (`timeout.response`) separately. These timeouts can be configured on all three levels as described in the configuration section above. +You can configure timeouts in a very flexible way. First of all, you can configure timeouts to establish the connection (`timeout.connection`) and to wait for the response (`timeout.response`) separately. These timeouts can be configured on all three levels as described in the configuration section above. === Error Handling -Whilst invoking a remote service an error may occur. This solution will automatically handle such errors and map them to a higher level `ServiceInvocationFailedException`. In general we separate two different types of errors: +Whilst invoking a remote service, an error may occur. This solution will automatically handle such errors and map them to a higher level `ServiceInvocationFailedException`. In general, we separate two different types of errors: * *Network error* + -In such case (host not found, connection refused, time out, etc.) there is not even a response from the server. However, in advance to a low-level exception you will get a wrapped `ServiceInvocationFailedException` (with code `ServiceInvoke`) with a readable message containing the service that could not be invoked. +In such a case (host not found, connection refused, time out, etc.), there is not even a response from the server. However, in advance to a low-level exception you will get a wrapped `ServiceInvocationFailedException` (with code `ServiceInvoke`) with a readable message containing the service that could not be invoked. * *Service error* + -In case the service failed on the server-side the link:../guide-rest.asciidoc#error-results[error result] will be parsed and thrown as a `ServiceInvocationFailedException` with the received message and code. +In case the service failed on the server-side, the link:../guide-rest.asciidoc#error-results[error result] will be parsed and thrown as a `ServiceInvocationFailedException` with the received message and code. This allows to catch and handle errors when a service-invocation failed. You can even distinguish business errors from the server-side from technical errors and implement retry strategies or the like. -Further the created exception contains detailed contextual information about the serivce that failed (service interface class, method, URL) what makes it much easier to trace down errors. Here is an example from our tests: +Further, the created exception contains detailed contextual information about the service that failed (service interface class, method, URL), which makes it much easier to trace down errors. Here is an example from our tests: ``` While invoking the service com.devonfw.test.app.myexample.service.api.rest.MyExampleRestService#businessError[http://localhost:50178/app/services/rest/my-example/v1/business-error] the following error occurred: Test of business error. Probably the service is temporary unavailable. Please try again later. If the problem persists contact your system administrator. @@ -210,9 +210,9 @@ While invoking the service com.devonfw.test.app.myexample.service.api.rest.MyExa You may even provide your own implementation of `ServiceClientErrorFactory` instead to provide an own exception class for this purpose. -==== Handling Erros +==== Handling Errors -In case of a synchronous service invocation an error will be immediately thrown so you can sourround the call with a regular try-catch block: +In case of a synchronous service invocation, an error will be immediately thrown so you can surround the call with a regular try-catch block: [source,java] -------- @@ -236,7 +236,7 @@ In case of a synchronous service invocation an error will be immediately thrown } -------- -If you are using asynchronous service invocation an error can occurr in a separate thread. Therefore you may and should define a custom error handler: +If you are using asynchronous service invocation, an error can occurr in a separate thread. Therefore, you may and should define a custom error handler: [source,java] -------- @@ -254,21 +254,21 @@ If you are using asynchronous service invocation an error can occurr in a separa } -------- -The error handler consumes `Throwable` and not only `RuntimeException` so you can get notified even in case of an unexpected `OutOfMemoryError`, `NoClassDefFoundError`, or other technical problems. Please note that the error handler may also be called from the thread calling the service (e.g. if already creating the request fails). The default error handler used if no custom handler is set will only log the error and do nothing else. +The error handler consumes `Throwable`, and not only `RuntimeException`, so you can get notified even in case of an unexpected `OutOfMemoryError`, `NoClassDefFoundError`, or other technical problems. Please note that the error handler may also be called from the thread calling the service (e.g. if already creating the request fails). The default error handler used if no custom handler is set will only log the error and do nothing else. === Logging -By default this solution will log all invocations including the URL of the invoked service, success or error status flag and the duration in seconds (with decimal nano precision as available). Therefore you can easily monitor the status and performance of the service invocations. Here is an example from our tests: +By default, this solution will log all invocations including the URL of the invoked service, success or error status flag and the duration in seconds (with decimal nano precision as available). Therefore, you can easily monitor the status and performance of the service invocations. Here is an example from our tests: ``` Invoking service com.devonfw.test.app.myexample.service.api.rest.MyExampleRestService#greet[http://localhost:50178/app/services/rest/my-example/v1/greet/John%20Doe%20%26%20%3F%23] took PT20.309756622S (20309756622ns) and succeded with status 200. ``` === Resilience -Resilience adds a lot of complexity and that typically means that addressing this here would most probably result in not being up-to-date and not meeting all requirements. Therefore we recommend something completely different: the _sidecar_ approach (based on https://docs.microsoft.com/en-us/azure/architecture/patterns/sidecar[sidecar pattern]). This means that you use a generic proxy app that runs as a separate process on the same host, VM, or container of your actual application. Then in your app you are calling the service via the sidecar proxy on `localhost` (service discovery URL is e.g. `http://localhost:8081/${app}/services/${type}`) that then acts as proxy to the actual remote service. Now aspects such as resilience with circuit breaking and the actual service discovery can be configured in the sidecar proxy app and independent of your actual application. Therefore, you can even share and reuse configuration and experience with such a sidecar proxy app even across different technologies (Java, .NET/C#, Node.JS, etc.). Further, you do not pollute the technology stack of your actual app with the infrastructure for resilience, throttling, etc. and can update the app and the side-card independently when security-fixes are available. +Resilience adds a lot of complexity, which typically means that addressing this here would most probably result in not being up-to-date and not meeting all requirements. Therefore, we recommend something completely different: the _sidecar_ approach (based on https://docs.microsoft.com/en-us/azure/architecture/patterns/sidecar[sidecar pattern]). This means that you use a generic proxy app that runs as a separate process on the same host, VM, or container of your actual application. Then, in your app, you call the service via the sidecar proxy on `localhost` (service discovery URL is e.g. `http://localhost:8081/${app}/services/${type}`) that then acts as proxy to the actual remote service. Now aspects such as resilience with circuit breaking and the actual service discovery can be configured in the sidecar proxy app, independent of your actual application. Therefore, you can even share and reuse configuration and experience with such a sidecar proxy app even across different technologies (Java, .NET/C#, Node.JS, etc.). Further, you do not pollute the technology stack of your actual app with the infrastructure for resilience, throttling, etc. and can update the app and the sidecar independently when security-fixes are available. Various implementations of such sidecar proxy apps are available as free open source software. -Our recommendation in devonfw is to use https://istio.io/[istio]. This not only provides such a side-car but also an entire management solution for service-mesh making administration and maintenance much easier. Platforms like OpenShift support this out of the box. +Our recommendation in devonfw is to use https://istio.io/[istio]. This not only provides such a side-car, but also an entire management solution for service-mesh, making administration and maintenance much easier. Platforms like OpenShift support this out of the box. -However, if you are looking for details about side-car implementations for services you can have a look at the following links: +However, if you are looking for details about side-car implementations for services, you can have a look at the following links: * Netflix Sidecar - see http://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#_polyglot_support_with_sidecar[Spring Cloud Netflix docs] * https://lyft.github.io/envoy/[Envoy] - see https://dzone.com/articles/microservices-patterns-with-envoy-sidecar-proxy-pa[Microservices Patterns With Envoy Sidecar Proxy] From eb0078efb904a58c753552e977297e63fbd942f6 Mon Sep 17 00:00:00 2001 From: Vicaria Barker Date: Tue, 7 Dec 2021 10:46:25 +0100 Subject: [PATCH 5/9] Added the dependencies that need to be included in Maven project, included a orderBy-clause in the example and explained where(), orderBy(), and groupBy() statements --- documentation/guide-jpa-query.asciidoc | 63 +++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/documentation/guide-jpa-query.asciidoc b/documentation/guide-jpa-query.asciidoc index 3126da70..1e7058d5 100644 --- a/documentation/guide-jpa-query.asciidoc +++ b/documentation/guide-jpa-query.asciidoc @@ -23,7 +23,59 @@ SELECT line FROM OrderLineEntity line WHERE line.order.id = :orderId ---- == Dynamic Queries -For dynamic queries we use http://www.querydsl.com/[QueryDSL]. It allows to implement queries in a powerful but readable and type-safe way (unlike Criteria API). If you already know JPQL you will quickly be able to read and write QueryDSL code. It feels like JPQL but implemented in Java instead of plain text. +For dynamic queries, we use http://www.querydsl.com/[QueryDSL]. It allows to implement queries in a powerful but readable and type-safe way (unlike Criteria API). If you already know JPQL, you will quickly be able to read and write QueryDSL code. It feels like JPQL but implemented in Java instead of plain text. + +To use QueryDSL in your Maven project, add the following dependencies: + +[source,xml] +---- + + + + com.querydsl + querydsl-apt + ${querydsl.version} + provided + + + + com.querydsl + querydsl-jpa + ${querydsl.version} + + + +---- + +Next, configure the annotation processing tool (APT) plugin: + +[source,xml] +---- + + + + ... + + com.mysema.maven + apt-maven-plugin + 1.1.3 + + + + process + + + target/generated-sources/java + com.querydsl.apt.jpa.JPAAnnotationProcessor + + + + + ... + + + +---- Here is an example from our sample application: @@ -49,14 +101,21 @@ Here is an example from our sample application: if ((name != null) && (!name.isEmpty())) { query.where(dish.name.eq(name)); } + query.orderBy(dish.price.asc(), dish.name.asc()); return query.fetch(); } ---- -In this example we use the so called Q-types (`QDishEntity`). These are classes generated at build time by the QueryDSL annotation processor from entity classes. The Q-type classes can be used as static types representative of the original entity class. +In this example, we use the so called Q-types (`QDishEntity`). These are classes generated at build time by the QueryDSL annotation processor from entity classes. The Q-type classes can be used as static types representative of the original entity class. + +The `query.from(dish)` method call defines the query source, in this case the `dish` table. The `where` call defines a filter. For example, The first call uses the `goe` operator to filter out any dishes that are not greater or equal to the minimal price. Further operators can be found https://querydsl.com/static/querydsl/4.4.0/apidocs/com/querydsl/core/types/dsl/ComparableExpression.html[here]. + +The `orderBy` method is used to sort the query results according to certain criteria. Here, we sort the results first by their price and then by their name, both in ascending order. To sort in descending order, use `.desc()`. To partition query results into groups of rows, see the https://querydsl.com/static/querydsl/4.1.3/reference/html_single/#d0e377[groupBy] method. For spring, devon4j provides another approach that you can use for your Spring applications to implement QueryDSL logic without having to use these metaclasses. An example can be found link:spring/guide-querydsl-spring.asciidoc[here]. + + == Using Wildcards For flexible queries it is often required to allow wildcards (especially in xref:dynamic_queries[dynamic queries]). While users intuitively expect glob syntax the SQL and JPQL standards work different. Therefore a mapping is required. devonfw provides this on a lower level by https://github.com/devonfw/devon4j/blob/develop/modules/basic/src/main/java/com/devonfw/module/basic/common/api/query/LikePatternSyntax.java[LikePatternSyntax] and on a high level by https://github.com/devonfw/devon4j/blob/develop/modules/jpa-basic/src/main/java/com/devonfw/module/jpa/dataaccess/api/QueryUtil.java#L54[QueryUtil] (see https://github.com/devonfw/devon4j/blob/develop/modules/jpa-basic/src/main/java/com/devonfw/module/jpa/dataaccess/api/QueryHelper.java#L199[QueryHelper.newStringClause(...)]). From c69ddd516005782416115dee81b21fb6f50a9e1f Mon Sep 17 00:00:00 2001 From: Vicaria Barker Date: Fri, 10 Dec 2021 09:39:53 +0100 Subject: [PATCH 6/9] Fixed indentation, links to querydsl, and small mistakes --- documentation/guide-jpa-query.asciidoc | 44 +++++++++++++------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/documentation/guide-jpa-query.asciidoc b/documentation/guide-jpa-query.asciidoc index 1e7058d5..2d3dcf61 100644 --- a/documentation/guide-jpa-query.asciidoc +++ b/documentation/guide-jpa-query.asciidoc @@ -53,26 +53,26 @@ Next, configure the annotation processing tool (APT) plugin: ---- - - ... - - com.mysema.maven - apt-maven-plugin - 1.1.3 - - - - process - - - target/generated-sources/java - com.querydsl.apt.jpa.JPAAnnotationProcessor - - - - - ... - + + ... + + com.mysema.maven + apt-maven-plugin + 1.1.3 + + + + process + + + target/generated-sources/java + com.querydsl.apt.jpa.JPAAnnotationProcessor + + + + + ... + ---- @@ -108,9 +108,9 @@ Here is an example from our sample application: In this example, we use the so called Q-types (`QDishEntity`). These are classes generated at build time by the QueryDSL annotation processor from entity classes. The Q-type classes can be used as static types representative of the original entity class. -The `query.from(dish)` method call defines the query source, in this case the `dish` table. The `where` call defines a filter. For example, The first call uses the `goe` operator to filter out any dishes that are not greater or equal to the minimal price. Further operators can be found https://querydsl.com/static/querydsl/4.4.0/apidocs/com/querydsl/core/types/dsl/ComparableExpression.html[here]. +The `query.from(dish)` method call defines the query source, in this case the `dish` table. The `where` method defines a filter. For example, The first call uses the `goe` operator to filter out any dishes that are not greater or equal to the minimal price. Further operators can be found https://querydsl.com/static/querydsl/latest/apidocs/com/querydsl/core/types/dsl/ComparableExpression.html[here]. -The `orderBy` method is used to sort the query results according to certain criteria. Here, we sort the results first by their price and then by their name, both in ascending order. To sort in descending order, use `.desc()`. To partition query results into groups of rows, see the https://querydsl.com/static/querydsl/4.1.3/reference/html_single/#d0e377[groupBy] method. +The `orderBy` method is used to sort the query results according to certain criteria. Here, we sort the results first by their price and then by their name, both in ascending order. To sort in descending order, use `.desc()`. To partition query results into groups of rows, see the https://querydsl.com/static/querydsl/latest/reference/html_single/#d0e377[groupBy] method. For spring, devon4j provides another approach that you can use for your Spring applications to implement QueryDSL logic without having to use these metaclasses. An example can be found link:spring/guide-querydsl-spring.asciidoc[here]. From 466c0d1e535fd1f79663160219dfbf493047a99f Mon Sep 17 00:00:00 2001 From: Vicaria Barker Date: Tue, 14 Dec 2021 11:22:55 +0100 Subject: [PATCH 7/9] Specified usage of JPA and linked other supported modules in guide-jpa-query. Also linked to the REST guide in decision-service-framework --- documentation/guide-jpa-query.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/guide-jpa-query.asciidoc b/documentation/guide-jpa-query.asciidoc index 2d3dcf61..55a0a25c 100644 --- a/documentation/guide-jpa-query.asciidoc +++ b/documentation/guide-jpa-query.asciidoc @@ -23,7 +23,7 @@ SELECT line FROM OrderLineEntity line WHERE line.order.id = :orderId ---- == Dynamic Queries -For dynamic queries, we use http://www.querydsl.com/[QueryDSL]. It allows to implement queries in a powerful but readable and type-safe way (unlike Criteria API). If you already know JPQL, you will quickly be able to read and write QueryDSL code. It feels like JPQL but implemented in Java instead of plain text. +For dynamic queries, we use the https://spring.io/projects/spring-data-jpa[JPA] module for http://www.querydsl.com/[QueryDSL]. Querydsl also supports other modules such as http://querydsl.com/static/querydsl/latest/reference/html/ch02s07.html[MongoDB], and http://querydsl.com/static/querydsl/latest/reference/html/ch02s05.html[Apache Lucene]. It allows to implement queries in a powerful but readable and type-safe way (unlike Criteria API). If you already know JPQL, you will quickly be able to read and write QueryDSL code. It feels like JPQL but implemented in Java instead of plain text. To use QueryDSL in your Maven project, add the following dependencies: From e88c475d0dd3c931183c4ab460e73a5f72f998af Mon Sep 17 00:00:00 2001 From: Vicaria Barker Date: Tue, 14 Dec 2021 11:25:13 +0100 Subject: [PATCH 8/9] Specified usage of JPA and linked other supported modules in guide-jpa-query. Also linked to the REST guide in decision-service-framework --- documentation/decision-service-framework.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/decision-service-framework.asciidoc b/documentation/decision-service-framework.asciidoc index 237e7ca7..ac2d9a2c 100644 --- a/documentation/decision-service-framework.asciidoc +++ b/documentation/decision-service-framework.asciidoc @@ -6,7 +6,7 @@ toc::[] We need to choose which framework(s) we want to use for building services. For the devonfw, we focus on a standard API, if available. However, we also want to recommend an implementation. While projects would still be able to choose whatever they want, we want to suggest the best, most robust, and established solution. This way, projects do not have to worry about the decision and can rely on a production-ready framework without running into any trouble. Also, besides the standard, the configuration of the implementation framework differs, so we want to give instructions in the documentation and by our sample application. This is why, in the end, the implementation also matters. If a project has a customer demand to use something else, the project has to take care of it. We will always suggest and "support" ONE solution. == REST Services -For REST services, devonfw relies on the JAX-RS standard (and NOT on spring-mvc with its proprietary annotations). https://github.com/eclipse-ee4j/jaxrs-api[JAX-RS] (Jakarta RESTful Web Services) is a Java programming language API to develop web services following the Representational State Transfer (REST) architectural pattern. +For link:guide-rest.asciidoc[REST] services, devonfw relies on the JAX-RS standard (and NOT on spring-mvc with its proprietary annotations). https://github.com/eclipse-ee4j/jaxrs-api[JAX-RS] (Jakarta RESTful Web Services) is a Java programming language API to develop web services following the Representational State Transfer (REST) architectural pattern. For https://cxf.apache.org/[Apache CXF], the spring container was the first choice, but container abstraction has been properly introduced by design, so it can be used in JEE application servers. Apache CXF is a services framework that helps to build and develop services using frontend programming APIs, such as JAX-RS. Everything works smoothly in our sample application, and in addition, we collected feedback from various projects utilizing CXF, either with XML or JSON, with reported success in production. Therefore, we decided to use Apache CXF for Spring. For Quarkus applications, devon4j recommends to use https://github.com/resteasy/resteasy[RESTEasy], which is a JAX-RS implementation aimed at providing productivity frameworks for developing client and server RESTful applications and services in Java. From 1e81992d18937fff35836c50a7bd04045f1c08c9 Mon Sep 17 00:00:00 2001 From: Vicaria Barker Date: Wed, 15 Dec 2021 11:10:29 +0100 Subject: [PATCH 9/9] Swapped the Spring link with a Querydsl link and replaced every mention of QueryDSL with Querydsl --- documentation/guide-jpa-query.asciidoc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/documentation/guide-jpa-query.asciidoc b/documentation/guide-jpa-query.asciidoc index 55a0a25c..6e94e92d 100644 --- a/documentation/guide-jpa-query.asciidoc +++ b/documentation/guide-jpa-query.asciidoc @@ -23,9 +23,9 @@ SELECT line FROM OrderLineEntity line WHERE line.order.id = :orderId ---- == Dynamic Queries -For dynamic queries, we use the https://spring.io/projects/spring-data-jpa[JPA] module for http://www.querydsl.com/[QueryDSL]. Querydsl also supports other modules such as http://querydsl.com/static/querydsl/latest/reference/html/ch02s07.html[MongoDB], and http://querydsl.com/static/querydsl/latest/reference/html/ch02s05.html[Apache Lucene]. It allows to implement queries in a powerful but readable and type-safe way (unlike Criteria API). If you already know JPQL, you will quickly be able to read and write QueryDSL code. It feels like JPQL but implemented in Java instead of plain text. +For dynamic queries, we use the http://querydsl.com/static/querydsl/latest/reference/html/ch02.html[JPA] module for http://www.querydsl.com/[Querydsl]. Querydsl also supports other modules such as http://querydsl.com/static/querydsl/latest/reference/html/ch02s07.html[MongoDB], and http://querydsl.com/static/querydsl/latest/reference/html/ch02s05.html[Apache Lucene]. It allows to implement queries in a powerful but readable and type-safe way (unlike Criteria API). If you already know JPQL, you will quickly be able to read and write Querydsl code. It feels like JPQL but implemented in Java instead of plain text. -To use QueryDSL in your Maven project, add the following dependencies: +To use Querydsl in your Maven project, add the following dependencies: [source,xml] ---- @@ -106,13 +106,13 @@ Here is an example from our sample application: } ---- -In this example, we use the so called Q-types (`QDishEntity`). These are classes generated at build time by the QueryDSL annotation processor from entity classes. The Q-type classes can be used as static types representative of the original entity class. +In this example, we use the so called Q-types (`QDishEntity`). These are classes generated at build time by the Querydsl annotation processor from entity classes. The Q-type classes can be used as static types representative of the original entity class. The `query.from(dish)` method call defines the query source, in this case the `dish` table. The `where` method defines a filter. For example, The first call uses the `goe` operator to filter out any dishes that are not greater or equal to the minimal price. Further operators can be found https://querydsl.com/static/querydsl/latest/apidocs/com/querydsl/core/types/dsl/ComparableExpression.html[here]. The `orderBy` method is used to sort the query results according to certain criteria. Here, we sort the results first by their price and then by their name, both in ascending order. To sort in descending order, use `.desc()`. To partition query results into groups of rows, see the https://querydsl.com/static/querydsl/latest/reference/html_single/#d0e377[groupBy] method. -For spring, devon4j provides another approach that you can use for your Spring applications to implement QueryDSL logic without having to use these metaclasses. An example can be found link:spring/guide-querydsl-spring.asciidoc[here]. +For spring, devon4j provides another approach that you can use for your Spring applications to implement Querydsl logic without having to use these metaclasses. An example can be found link:spring/guide-querydsl-spring.asciidoc[here]. @@ -140,9 +140,9 @@ Pageable pageable = PageRequest.of(page, size); Page dishes = dishRepository.findAll(pageable); ---- -=== Paging with QueryDSL +=== Paging with Querydsl -Pagination is also supported for dynamic queries with QueryDSL: +Pagination is also supported for dynamic queries with Querydsl: [source,java] ----