Implementing DELETE operation
Table of contents
In this step, we'll write a method for deleting a blog post. By completing this step, you'll learn to map your service with the HTTP DELETE (@Delete
) method, customize an exception handler, and use a blocking task executor.
What you need
You need to have the following files obtained from previous steps. You can always download the full version, instead of creating one yourself.
Main.java
BlogPost.java
BlogService.java
BlogServiceTest.java
1. Map HTTP method
Let's start mapping the HTTP DELETE method with our service method:
- Declare a service method,
deleteBlogPost()
in the classBlogService
. - Map this service method with the HTTP DELETE method by adding the
@Delete
annotation. - Bind the endpoint
/blogs
to the method.
import com.linecorp.armeria.server.annotation.Delete;
public final class BlogService {
...
@Delete("/blogs")
public void deleteBlogPost(int id) {
// Delete a blog post
}
}
2. Handle parameters
Let's take the blog post ID (id
) as a path parameter for identifying the post to delete.
Take in the ID value as a path parameter by adding
/blogs/:id
to the@Delete
annotation.Inject the path parameter to the service method by annotating the parameter with
@Param
.BlogService.javaimport com.linecorp.armeria.server.annotation.Param; public final class BlogService { ... @Delete("/blogs/:id") public void deleteBlogPost(@Param int id) { // Delete a blog post } }
3. Implement service code
In this step, write the code to delete a blog post, handle an exception, and block the operation.
Delete a blog post
Deleting a given blog post in this tutorial means removing a blog post from the map, blogPosts
. However, in real services you would be performing this action on a database.
To delete a blog post, copy line 3 into the deleteBlogPost()
method.
1@Delete("/blogs/:id")
2public void deleteBlogPost(@Param int id) {
3 BlogPost removed = blogPosts.remove(id);
4}
Handle exceptions
What if there is no such post to delete? We can check if the blog exists before attempting to remove the blog post. Here, let's handle it after the attempt.
Throw an
IllegalArgumentException
if no blog post exists with a given ID.BlogService.java@Delete("/blogs/:id") public void deleteBlogPost(@Param int id) { ... if (removed == null) { throw new IllegalArgumentException("The blog post does not exist. ID: " + id); } }
Create an exception handler for the blog service:
- Create a file,
BadRequestExceptionHandler.java
. - In the file, declare a custom exception handler implementing Armeria's
ExceptionHandlerFunction
interface.
BadRequestExceptionHandler.javapackage example.armeria.server.blog; import com.linecorp.armeria.server.annotation.ExceptionHandlerFunction; import com.fasterxml.jackson.databind.ObjectMapper; public class BadRequestExceptionHandler implements ExceptionHandlerFunction { private static final ObjectMapper mapper = new ObjectMapper(); }
- Create a file,
Implement your own exception handler by overriding the default
handleException()
method. Add a code block for handling theIllegalArgumentException
thrown. For this tutorial, return a BAD REQUEST as the response.BadRequesExceptionHandler.javaimport com.fasterxml.jackson.databind.node.ObjectNode; import com.linecorp.armeria.common.HttpResponse; import com.linecorp.armeria.common.HttpStatus; import com.linecorp.armeria.server.ServiceRequestContext; public class BadRequestExceptionHandler implements ExceptionHandlerFunction { ... @Override public HttpResponse handleException(ServiceRequestContext ctx, HttpRequest req, Throwable cause) { if (cause instanceof IllegalArgumentException) { String message = cause.getMessage(); ObjectNode objectNode = mapper.createObjectNode(); objectNode.put("error", message); return HttpResponse.ofJson(HttpStatus.BAD_REQUEST, objectNode); } return ExceptionHandlerFunction.fallthrough(); } }
Assign the exception handler to the
deleteBlogPost()
method by annotating thedeletePost()
method with the@ExceptionHandler
as follows.BlogService.javaimport com.linecorp.armeria.server.annotation.ExceptionHandler; @Delete("/blogs/:id") @ExceptionHandler(BadRequestExceptionHandler.class) public void deleteBlogPost(@Param int id) { ... }
Add blocking
With real services, accessing and operating on a database takes time. We need to hand over such blocking tasks to blocking task executor so that the EventLoop isn't blocked. There are a few options to implement this; we'll annotate our service method with the @Blocking
.
import com.linecorp.armeria.server.annotation.Blocking;
public final class BlogService {
...
@Blocking
@Delete("/blogs/:id")
@ExceptionHandler(BadRequestExceptionHandler.class)
public void deleteBlogPost(@Param int id) { ... }
}
4. Return response
We've already handled returning the not found error in the exception handling section. Here, we'll return a response for successful deletion.
- Replace the return type of the
deleteBlogPost()
method fromvoid
toHttpResponse
. - Return a response using Armeria's
HttpResponse
, containingHttpStatus.NO_CONTENT
.
import com.linecorp.armeria.common.HttpResponse;
public final class BlogService {
...
@Blocking
@Delete("/blogs/:id")
@ExceptionHandler(BadRequestExceptionHandler.class)
public HttpResponse deleteBlogPost(@Param int id) {
...
return HttpResponse.of(HttpStatus.NO_CONTENT);
}
}
5. Test an error case
Add a test method as follows to test if our exception handler is working properly.
import static org.assertj.core.api.Assertions.assertThat;
@Test
@Order(5)
void badRequestExceptionHandlerWhenTryingDeleteMissingBlogPost() throws JsonProcessingException {
final WebClient client = WebClient.of(server.httpUri());
final AggregatedHttpResponse res = client.delete("/blogs/100").aggregate().join();
assertThat(res.status()).isSameAs(HttpStatus.BAD_REQUEST);
assertThatJson(res.contentUtf8()).isEqualTo("{\"error\":\"The blog post does not exist. ID: 100\"}");
}
Run all the test cases on your IDE or using Gradle. Check that you see the test is passed.
You can test this also with Armeria's Documentation service. See Using DocService after adding service methods for instructions.
Next step
In this step, we've written a method for a DELETE operation and used Armeria's annotations; @Delete
, @Param
, @ExceptionHandler
and @Blocking
.
We've come to the end of this tutorial. Next, try adding more service methods to the tutorial or have a go at developing a service of your own.