Implementing CREATE operation
Table of contents
In this step, you'll write a service method for creating a blog post and a test method to verify the service method.
By completing this step, you'll learn to map your service with the HTTP POST (@Post) method,
make your own request converter (@RequestConverter), and utilize a server extension for testing (ServerExtension).
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.javaBlogPost.javaBlogService.java
1. Map HTTP method
Let's start mapping the HTTP POST method with our service method:
- Declare a service method,
createBlogPost(), in the classBlogService. - Map this service method with the HTTP POST method by adding the
@Postannotation. - Bind the endpoint
/blogsto the method.
import com.linecorp.armeria.server.annotation.Post;
public final class BlogService {
...
@Post("/blogs")
public void createBlogPost(BlogPost blogPost) {}
}2. Handle parameters
Let's receive blog post information through a request body. Armeria's request converter converts request parameters in HTTP messages into Java objects for you. In the request converter, we define what keys of a JSON object to map with what properties of a Java object.
Let's first write a request converter and then register the request converter to the service method.
Write a request converter
Armeria's request converter converts a request body from a client into a Java object for you.
We can use Armeria's default JacksonRequestConverterFunction as is, but here let's give a go at customizing a request converter for our blog post requests. We want to convert blog post details into a Java object.
Create a
BlogPostRequestConverter.javafile and declare a class, implementing theRequestConverterFunctioninterface. For the sake of simplicity, generate impromptu IDs for this tutorial.BlogRequestConverter.javapackage example.armeria.server.blog; import com.fasterxml.jackson.databind.ObjectMapper; import com.linecorp.armeria.server.annotation.RequestConverterFunction; import java.util.concurrent.atomic.AtomicInteger; final class BlogPostRequestConverter implements RequestConverterFunction { private static final ObjectMapper mapper = new ObjectMapper(); private AtomicInteger idGenerator = new AtomicInteger(); // Blog post ID }Add a method retrieving a value of a given key in a JSON object:
BlogRequestConverter.javaimport com.fasterxml.jackson.databind.JsonNode; final class BlogPostRequestConverter implements RequestConverterFunction { ... static String stringValue(JsonNode jsonNode, String field) { JsonNode value = jsonNode.get(field); if (value == null) { throw new IllegalArgumentException(field + " is missing!"); } return value.textValue(); } }Customize the default
convertRequest()method as follows.BlogRequestConverter.javaimport com.linecorp.armeria.server.ServiceRequestContext; import com.linecorp.armeria.common.AggregatedHttpRequest; import com.linecorp.armeria.common.annotation.Nullable; import java.lang.reflect.ParameterizedType; final class BlogPostRequestConverter implements RequestConverterFunction { ... @Override public Object convertRequest(ServiceRequestContext ctx, AggregatedHttpRequest request, Class<?> expectedResultType, @Nullable ParameterizedType expectedParameterizedResultType) throws Exception { if (expectedResultType == BlogPost.class) { JsonNode jsonNode = mapper.readTree(request.contentUtf8()); int id = idGenerator.getAndIncrement(); String title = stringValue(jsonNode, "title"); String content = stringValue(jsonNode, "content"); return new BlogPost(id, title, content); // Create an instance of BlogPost object } return RequestConverterFunction.fallthrough(); } ... }
Register a request converter
In this step, assign the request converter we customized to our service method. Annotate the service method with @RequestConverter and specify the RequestConverterFunction class as BlogPostRequestConverter.class.
import com.linecorp.armeria.server.annotation.RequestConverter;
public final class BlogService {
...
@Post("/blogs")
@RequestConverter(BlogPostRequestConverter.class)
public void createBlogPost(BlogPost blogPost) {
// Implement blog service
}
}3. Implement service code
When the request for creation is received, our request converter creates an instance of a blog post object for us. We want to save the blog post object in the map (blogPosts) created in the BlogService class.
Let's store the blog post information in the map by adding line 4, in the createBlogPost() method.
1@Post("/blogs")
2@RequestConverter(BlogPostRequestConverter.class)
3public void createBlogPost(BlogPost blogPost) {
4 blogPosts.put(blogPost.getId(), blogPost);
5}4. Return response
Now, it's time to return a response to our client. As the response, return the information received, with additional information including the ID of the post, created time, plus the modified time which would be identical to the created time.
Let's return a response for blog post creation:
Replace the return type of the
createBlogPost()method fromvoidtoHttpResponse.Create and return an HTTP response using Armeria's
HttpResponsewith the information of the post created.BlogService.javaimport com.linecorp.armeria.common.HttpResponse; public final class BlogService { ... public HttpResponse createBlogPost(BlogPost blogPost) { ... return HttpResponse.ofJson(blogPost); } }
5. Create a test file
Let's start writing test code. We'll use test code to verify what we implement along the way.
Create the BlogServiceTest.java file as follows.
You can see the full version of the file here.
package example.armeria.server.blog;
import com.fasterxml.jackson.databind.ObjectMapper;
class BlogServiceTest {
private static final ObjectMapper mapper = new ObjectMapper();
}6. Register a ServerExtension
Armeria's ServerExtension automatically handles set-up and tear-down of a server for testing.
This is convenient as it eliminates the need to execute the main method to set up a server before running our tests.
In the BlogServiceTest class, register a ServerExtension as follows.
Note that the service instance is added to the configuration.
import org.junit.jupiter.api.extension.RegisterExtension;
import com.linecorp.armeria.server.ServerBuilder;
import com.linecorp.armeria.testing.junit5.server.ServerExtension;
class BlogServiceTest {
...
@RegisterExtension
static final ServerExtension server = new ServerExtension() {
@Override
protected void configure(ServerBuilder sb) throws Exception {
sb.annotatedService(new BlogService());
}
};
}7. Test creating a blog post
Let's test if we can create a blog post.
In the
BlogServiceTestclass, add a private method as follows.BlogServiceTest.javaimport java.util.Map; import com.fasterxml.jackson.core.JsonProcessingException; import com.linecorp.armeria.common.HttpRequest; import com.linecorp.armeria.common.MediaType; ... private static HttpRequest createBlogPostRequest(Map<String, String> content) throws JsonProcessingException { return HttpRequest.builder() .post("/blogs") .content(MediaType.JSON_UTF_8, mapper.writeValueAsString(content)) .build(); }Add a test method as follows to test creating a blog post.
BlogServiceTest.javaimport static net.javacrumbs.jsonunit.fluent.JsonFluentAssert.assertThatJson; import org.junit.jupiter.api.Test; import com.linecorp.armeria.client.WebClient; import com.linecorp.armeria.common.AggregatedHttpResponse; ... @Test void createBlogPost() throws JsonProcessingException { final WebClient client = WebClient.of(server.httpUri()); final HttpRequest request = createBlogPostRequest(Map.of("title", "My first blog", "content", "Hello Armeria!")); final AggregatedHttpResponse res = client.execute(request).aggregate().join(); final Map<String, Object> expected = Map.of("id", 0, "title", "My first blog", "content", "Hello Armeria!"); assertThatJson(res.contentUtf8()).whenIgnoringPaths("createdAt", "modifiedAt") .isEqualTo(mapper.writeValueAsString(expected)); }Run the test case on your IDE or using Gradle.
./gradlew testThe service worked as expected if you see the test case 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 to implement a CREATE operation and used Armeria's annotations; @Post and @RequestConverter.
We've also registered ServerExtension to our test and written a test method.
Next, at Step 5. Implement READ, we'll implement a READ operation to read a single post and also multiple posts.