- General
- Best Practices in detail
Always ensure the SDKs in use are up to date this is important as to ensure the customer do have all the latest features offered by the SDK including security and vulnerability fixes.
Ensure to read the documentation carefully in order to correctly and properly implement and/or integrate the SDK.
Ensure issues are reported as soon as they are noticed or spotted as this ensures the SDK is working properly and serves its purpose.
Issues encountered while using or trying to use the SDK can be reported in the SDK repository issues section or contact the support team directly. Reported issues should have as much details as possible (details such as the SDK version, the specific error message, log or stack trace.You can find the template here
As the saying goes, no software project is 100% complete so is our SDK, as a customer, if you see areas where the SDK can be improved you are welcome to raise an issue or better still raise a PR. Part of the version 2 SDKs are generated automatically, so please be careful not to raise PRs for the generated part, please see this for more on generated SDKs.
Always ensure all the credentials (e.g clientId, clientSecret) used in the SDK are not exposed in anyway to the outside world, use environment variables- if necessary to store credentials.
There are tests within the SDK repository where you can learn how to use certain SDK functionalities just by looking at the tests. Also, we have an example folder that contains examples of how to use the SDK for different environments. Other than that, in this file, there are also some useful examples, on how to migrate from Java SDK v1 to Java SDK V2.
We have middlewares available that can be used in base of the needs see here
This is the list of middlewares available and how to use them:
The error middleware translates HTTP errors to Exceptions.
The ApiRootBuilder can be used to configure it.
ApiRootBuilder.of().withErrorMiddleware();
If the defaultClient
builder method is used it will be configured. The builder also ensures that the ErrorMiddleware is always the last middleware in the call stack. The following examples shows custom configuration of the equivalent of the above method:
ApiRootBuilder.of().withErrorMiddleware(ErrorMiddleware.of(HttpExceptionFactory.of(ResponseSerializer.of())));
The OAuthMiddleware adds an authentication token to any request executed with the configured client. It will automatically try to reauthenticate once a token gets invalid. The defaultClient
builder method configures the client to use the client credentials flow.
The example shows the equivalent of the configured flow
ApiRootBuilder.of().withOAuthMiddleware(
OAuthMiddleware.of(
new OAuthHandler(
new InMemoryTokenSupplier(
...
)
)
)
)
The ApiRootBuilder also provides the methods for the different authentication flows e.g. for client credentials
ApiRootBuilder.of().withClientCredentialsFlow(
ClientCredentials.of().build(),
ServiceRegion.GCP_EUROPE_WEST1.getOAuthTokenUrl()
);
The PolicyBuilder allows the combination of different policies for failing requests. You can specify the error codes to retry, different retry strategy, timeout etc.
The middleware can retry on specific HTTP status codes or exception types
ApiRootBuilder.of().withPolicies(policyBuilder -> policyBuilder.withRetry(retry -> retry.maxRetries(3)
.statusCodes(Arrays.asList(HttpStatusCode.SERVICE_UNAVAILABLE_503,
HttpStatusCode.INTERNAL_SERVER_ERROR_500))))
For more examples see the PolicyMiddleware test.
The UserAgentMiddleware adds a User-Agent header to every request. It will be added by using the defaultClient
builder method. But can also be customized
ApiRootBuilder.of().withUserAgentSupplier(() -> "my-custom-useragent");
This middleware will log information like URL, HTTP method, status code, timing and correlation id. Using Debug or Trace level it will give more detailled information like the payload and HTTP headers. It also takes care of redacting sensitive information in headers and body before logging them.
The middleware can be configured with the withInternalLoggerMiddleware
method. The defaultClient
method will configure it too.
ApiRootBuilder.of().withInternalLoggerMiddleware(InternalLoggerMiddleware.of(ApiInternalLoggerFactory::get));
In case of concurrent modification errors this middleware will update the request with the actual resource version and retry the request itself. Please see also the documentation
The example shows the configuration to retry 3 times upon concurrent modification
ApiRootBuilder.of().withMiddleware(ConcurrentModificationMiddleware.of(3));
In case a resource can’t be found by the API the SDK will throw a NotFoundException. Often this is not wanted and the SDK should just return a null value. This can be achieved byusing the NotFoundExceptionMiddleware which can be configured at the ApiRoot builder
ApiRootBuilder.of().addNotFoundExceptionMiddleware();
The middleware can be limited to catch only specific request methods e.g. GET or by defining a request predicate
ApiRootBuilder.of().addNotFoundExceptionMiddleware(Collections.singleton(ApiHttpMethod.GET));
ApiRootBuilder.of().addNotFoundExceptionMiddleware(request -> request.getUri().getPath().contains("products"));
The client can be created once, it can be used by creating the ApiRoot object instance and reusing the object instance throughout the whole application.
ProjectApiRoot apiRoot = ApiRootBuilder.of()
.defaultClient(ClientCredentials.of()
.withClientId("your-client-id")
.withClientSecret("your-client-secret")
.withScopes("your-scopes")
.build(),
ServiceRegion.GCP_EUROPE_WEST1)
.build("your-project-key");
In case additional customization is needed for the underlying HTTP client, a client instance has to provided to the builder.
VrapHttpClient httpClient = new CtOkHttp4Client(
128, // maxRequests
128, // maxRequestsPerHost
builder -> builder.connectTimeout(Duration.ofMillis(200))
.writeTimeout(Duration.ofSeconds(60))
.readTimeout(Duration.ofSeconds(120)));
ProjectApiRoot apiRoot = ApiRootBuilder.of(httpClient)
.defaultClient(ClientCredentials.of()
.withClientId("your-client-id")
.withClientSecret("your-client-secret")
.withScopes("your-scopes")
.build(),
ServiceRegion.GCP_EUROPE_WEST1)
.build("my-project");
The ApiRoot classes implement the Closable interface and have to be closed before exiting the application to close all the used connections and thread pools. Closables could also be used in conjunction with the try-with-resources block to automatically close the client and used resources upon exiting the application.
Proxies have to be set up at http client level, as the example below showing it for OkHttp:
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy", 8080));
VrapHttpClient httpClient = new CtOkHttp4Client(builder -> builder.proxy(proxy));
ApiRoot apiRoot = ApiRootBuilder.of(httpClient)
.defaultClient(ClientCredentials.of()
.withClientId("your-client-id")
.withClientSecret("your-client-secret")
.withScopes("your-scopes")
.build(),
ServiceRegion.GCP_EUROPE_WEST1)
.build();
To create a product with 2 variant, it has to be done at ProductDraft level.
Here how to do it:
ProductDraft draft = ProductDraft.builder()
.productType(builder -> builder.key("show"))
.name(LocalizedStringBuilder.of().addValue("en", "foo").build())
.slug(LocalizedStringBuilder.of().addValue("en", "foo").build())
.plusVariants(builder -> builder.sku("abc").plusAttributes(attributeBuilder -> attributeBuilder.name("size").value("32")))
.plusVariants(builder -> builder.sku("sku").plusAttributes(attributeBuilder -> attributeBuilder.name("size").value("32")))
.build();
Product product = apiRoot.products().post(draft).executeBlocking().getBody();
We decided to not provide builder for the predicates as it was not intuitive to translate from the API predicates to the SDK predicate builders. To avoid the escaping issues while building a predicate string we advice to use predicate strings with input variables. This way the predicate can be static, while providing the dynamic parts as input variables. Please see https://docs.commercetools.com/api/predicates/query#input-variables and https://commercetools.github.io/commercetools-sdk-java-v2/javadoc/com/commercetools/docs/meta/Querying.html.
The ApiHttpResponse is the abstraction atop the HTTP transfer and doesn't know about the internal semantics of the HTTP body. The ApiHttpResponse::getMessage() method in this case returns the HTTP header message like OK, CONTINUE, BAD REQUEST. In case of an error the ErrorMiddleware will throw an exception. The HTTP body will be added as exception message string.
As the thrown exceptions are derived from the ApiHttpException class which has a getBodyAs helper method which is capable of deserializing the body string to the given specified class. For the Composable Commerce API this would be the ErrorResponse class. In case of a BadRequestException or ConcurrentModification the error response will be automatically deserialized.
apiRoot.products().post(draft).execute()
.exceptionally(throwable -> {
if (throwable instanceof com.commercetools.api.client.error.BadRequestException) {
((BadRequestException) throwable).getErrorResponse().getMessage();
}
if (throwable instanceof ApiHttpException) {
((ApiHttpException) throwable).getBodyAs(ErrorResponse.class).getMessage();
}
return null;
});
}