This is a REST API that implements a PATCH
endpoint following the conventions defined in RFC 6902. The API uses the zjsonpatch
library to performe the operations.
add
: Adds a new value to the specified path.replace
: Replaces the value at the specified path with a new value.remove
: Removes the value at the specified path.move
: Moves a value from one path to another.copy
: Copies a value from one path to another.test
: Tests whether a specified value matches the one at the given path.
Here are some examples of JSON Patch operations:
[
{ "op": "add", "path": "/fieldName", "value": "newValue" },
{ "op": "replace", "path": "/fieldName", "value": "updatedValue" },
{ "op": "remove", "path": "/fieldName" },
{ "op": "move", "from": "/sourceField", "path": "/destinationField" },
{ "op": "copy", "from": "/sourceField", "path": "/destinationField" },
{ "op": "test", "path": "/fieldName", "value": "expectedValue" }
]
@PatchMapping("/{id}")
public ResponseEntity<Object> patchById(
@Parameter(description = "ID of the resource to be modified")
@PathVariable(name = "id") Long id,
@Parameter(array = @ArraySchema(schema = @Schema(implementation = JsonPatchSchema.class)),
name = "JsonPatch",
description = "JSON Patch representation according to RFC 6902 for partial modification.")
@RequestBody JsonNode patch) throws IOException {
Object responseDto = orderService.patchById(id, patch);
return ResponseEntity
.status(HttpStatus.OK)
.eTag(etagService.generateEtag(responseDto))
.body(responseDto);
}
Validation steps:
- Retrieve the object using the provided ID and map it to a DTO constrained by Jakarta Validation annotations.
- Convert the object into a
JsonNode
using Jackson'sObjectMapper
and apply the patch withJsonPatch.apply()
. - Convert the patched object back to its original form using
treeToValue()
. - Use the
Validator
to check for constraint violations, which are handled by Spring Web'sBindingResult
and theExceptionHandler
forBadRequest
errors.
- The application uses UUID-based idempotency keys to identify identical requests.
- Idempotency keys are stored in Redis with a TTL of 10 minutes.
Service method for idempotency key validation:
@Transactional
@CachePut(value = "order", key = "#result.id")
@CacheEvict(value = "orderPage", allEntries = true)
public OrderResponseDto save(UUID idempotencyKey, OrderRequestDto newOrder) throws IdempotencyKeyConflictException {
if (idempotencyKeyService.findById(idempotencyKey)) {
throw new IdempotencyKeyConflictException(
String.format(
"The idempotency key '%s' has already been used. Please wait 10 minutes before reusing it.",
idempotencyKey.toString()
)
);
}
Order orderSaved = orderRepository.save(mapper.mapToObject(newOrder, Order.class));
idempotencyKeyService.save(idempotencyKey);
return mapper.mapToObject(orderSaved, OrderResponseDto.class);
}
- ETags are included in response headers for
POST
,PATCH
,GET
, andPUT
requests. - If the
If-None-Match
header matches the current resource version, the server responds with304 Not Modified
.
ETag generation example:
public <T> String generateEtag(T object) throws IOException {
String objectAsJsonString = objectMapper.writeValueAsString(object);
try (InputStream inputStream = new ByteArrayInputStream(objectAsJsonString.getBytes())) {
return generateETagHeaderValue(inputStream, false);
}
}
- Java JDK 21
- Docker Desktop
-
Start PostgreSQL and Redis using the
compose.yaml
file:docker compose up -d
-
Run the application:
./mvnw spring-boot:run
The application will be available at: http://localhost:8080
To access the API, use the following pattern:
http://localhost:8080/api/<resource_path>
- Access the Swagger documentation at: http://localhost:8080/api/swagger-ui/index.html
- For the API documentation in JSON format: http://localhost:8080/api/v3/api-docs