Implementing DELETE operation

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:

  1. Declare a service method, deleteBlogPost() in the class BlogService.
  2. Map this service method with the HTTP DELETE method by adding the @Delete annotation.
  3. Bind the endpoint /blogs to the method.
BlogService.java
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.

  1. Take in the ID value as a path parameter by adding /blogs/:id to the @Delete annotation.

  2. Inject the path parameter to the service method by annotating the parameter with @Param.

    BlogService.java
    import 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.

BlogService.Java
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.

  1. 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);
      }
    }
  2. Create an exception handler for the blog service:

    1. Create a file, BadRequestExceptionHandler.java.
    2. In the file, declare a custom exception handler implementing Armeria's ExceptionHandlerFunction interface.
    BadRequestExceptionHandler.java
    package 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();
    }
  3. Implement your own exception handler by overriding the default handleException() method. Add a code block for handling the IllegalArgumentException thrown. For this tutorial, return a BAD REQUEST as the response.

    BadRequesExceptionHandler.java
    import 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();
      }
    }
  4. Assign the exception handler to the deleteBlogPost() method by annotating the deletePost() method with the @ExceptionHandler as follows.

    BlogService.java
    import 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.

BlogService.java
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.

  1. Replace the return type of the deleteBlogPost() method from void to HttpResponse.
  2. Return a response using Armeria's HttpResponse, containing HttpStatus.NO_CONTENT.
BlogService.java
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.

BlogServiceTest.java
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.