Optimize schema object transformation with caching

Issue #291 resolved
Olivier von Dach created an issue

I would like to use web request validation based on an OpenAPI specification in a productive environment with a maximum response time of 10 ms. This number should be achievable with web requests with an array of 500 entries, and with an object of depth equal to two for each entry.

Former tickets #259, #250, #225, #214 and #213 could give a performance boost to the request validation.

Former ticket #273 illustrates a serious time-consuming operation related to the reading of the request body
and its conversion into a JsonNode; this ticket should be treated as well.

Another time-consuming operation is the systematic construction of a schema object based on the Swagger schema and its transformation by iterations to finally get a JsonNode. After analysis, I notice that this result is the same for each call on a given endpoint, so it is reasonable to want to keep this result for reuse.

Setting up a cache using a ConcurrentHashMap allows a drastic improvement in response time. The caching key must be able to identify the current API operation, including the target, i.e. a URL variable, a query parameter, or the body of the request.

For example:

public class SchemaValidator {
...

private Map<String, JsonNode> transformedSchemaCache = new ConcurrentHashMap<>();

public ValidationReport validate(@Nonnull final String operationId,
                                 @Nonnull final String value,
                                 @Nullable final Schema schema,
                                 @Nullable final String keyPrefix) {
    ...
    content = readContent(value, schema);

    final String validationKey = String.format("%s.%s", keyPrefix, operationId);
    final JsonNode schemaObject = readAndTransformSchemaObject(validationKey, schema, keyPrefix);
    ...                                 
}

private JsonNode readAndTransformSchemaObject(final String key, final Schema schema, @Nullable final String keyPrefix) {
    return transformedSchemaCache.computeIfAbsent(key, o -> {
        final ObjectNode schemaObject = Json.mapper().convertValue(schema, ObjectNode.class);
        final SchemaTransformationContext transformationContext = SchemaTransformationContext.create()
            .forRequest("request.body".equalsIgnoreCase(keyPrefix))
            .forResponse("response.body".equalsIgnoreCase(keyPrefix))
            .withAdditionalPropertiesValidation(additionalPropertiesValidationEnabled())
            .withDefinitions(definitions)
            .build();

        transformers.forEach(t -> t.apply(schemaObject, transformationContext));

        checkForKnownGotchasAndLogMessage(schemaObject);
        return schemaObject;
    });
}
...   
}

Convinced by the usefulness of an executable API specification for syntax and semantic live validation, I allow myself to set a high level of priority.

Thanks for your feedback,

Kind regards

Comments (2)

  1. Log in to comment