11 Service Deployment How to Build a Restful Web Service

11 Service Deployment - How to Build a RESTful Web Service #

After learning from the previous courses, we have mastered the implementation methods of the data access layer components in building a Spring Boot application. In the following courses, we will discuss another layer of components, which is the construction of the web service layer.

The interaction between services is an inevitable requirement for system design and development, which involves the publishing and consumption of web services. Today, let’s discuss how to publish web services in a Spring Boot application.

Service Interaction in the SpringCSS System #

Before introducing the specific technical system, let’s first clarify the application scenarios for service interaction in the SpringCSS case.

For a customer service system, the core business process is to generate customer tickets, which usually requires the use of user account information and the associated order information.

In the SpringCSS case, we have already built an order-service for managing orders in the previous courses. Next, we will separately build an account-service for managing user accounts and a customer-service for the core customer service.

Regarding the interaction between the three services, let’s take a look at the following diagram:

图片6

Interaction diagram of the three services in the SpringCSS case system

In fact, from the above diagram, we can already outline the execution flow of the core method generateCustomerTicket for generating tickets. Here, we first give the framework of the code, as shown in the following code:

public CustomerTicket generateCustomerTicket(Long accountId, String orderNumber) {

    // Create a customer ticket object

    CustomerTicket customerTicket = new CustomerTicket();

 

    // Get the Account object from the remote account-service

    Account account = getRemoteAccountById(accountId);

 

    // Get the Order object from the remote order-service

    Order order = getRemoteOrderByOrderNumber(orderNumber);

    // Set the CustomerTicket object and save it

    customerTicket.setAccountId(accountId);

    customerTicket.setOrderNumber(order.getOrderNumber());

    customerTicketRepository.save(customerTicket);

 

    return customerTicket;

}

Since the methods getRemoteAccountById and getRemoteOrderByOrderNumber both involve calling remote web services, we need to create the web service first.

And Spring Boot provides powerful component support for creating web services, simple and convenient. Let’s take a look together.

Creating RESTful Services #

In today’s distributed systems and microservice architectures, the RESTful style is a mainstream representation of web services.

In the following content, we will demonstrate how to create RESTful services using Spring Boot. But before that, let’s understand what a RESTful service is.

Understanding the RESTful Architecture Style #

You may have heard of the name REST but are not clear about its meaning.

REST (Representational State Transfer) is essentially an architectural style rather than a specification. This architectural style treats the access entry on the server side as a resource, and each resource is given a unique address using URI (Universal Resource Identifier) and uses standard HTTP methods in the transport protocol, such as the most common GET, PUT, POST, and DELETE.

The following table shows some specific examples of the RESTful style:

图片1

Examples of RESTful style

On the other hand, the data interaction between the client and the server involves serialization. There are many ways to implement the serialization of business objects for transmission in a network environment, and the common ones are text and binary.

Currently, JSON is a widely adopted serialization method, and in this course, we will use JSON as the default serialization method for all code examples.

Using Basic Annotations #

On the basis of the original Spring Boot application, we can expose RESTful-style HTTP endpoints by creating a series of Controller classes. Here, the concept of Controller is the same as that in Spring MVC. The simplest Controller class is shown in the following code:

@RestController
public class HelloController {

    @GetMapping("/")
    public String index() {
        return "Hello World!";
    }
}

From the above code, we can see that it includes two annotations, @RestController and @GetMapping.

Among them, the @RestController annotation inherits from the @Controller annotation in Spring MVC. As the name implies, it is an HTTP endpoint based on the RESTful style and will automatically use JSON to implement the serialization/deserialization of HTTP requests and responses.

With this feature, when building RESTful services, we can use the @RestController annotation instead of the @Controller annotation to simplify development.

Another annotation, @GetMapping, is similar to the @RequestMapping annotation in Spring MVC. Let’s take a look at the definition of the @RequestMapping annotation. The properties provided by this annotation are easy to understand, as shown in the following code:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping

public @interface RequestMapping {

    String name() default "";

 

    @AliasFor("path")

    String[] value() default {};

 

    @AliasFor("value")

    String[] path() default {};

 

    RequestMethod[] method() default {};

 

    String[] params() default {};

 

    String[] headers() default {};

 

    String[] consumes() default {};

    String[] produces() default {};

}

The definition of the @GetMapping annotation is very similar to that of @RequestMapping, except for the default use of RequestMethod.GET to specify the HTTP method, as shown in the following code:

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@RequestMapping(method = RequestMethod.GET)

public @interface GetMapping {

In addition to @GetMapping, Spring Boot 2 introduces a set of new annotations, including @PutMapping, @PostMapping, and @DeleteMapping. These annotations greatly facilitate developers in explicitly specifying HTTP request methods. Of course, you can still use the original @RequestMapping to achieve the same effect.

Let’s look at a more specific example, which shows the AccountController in the account-service.

@RestController

@RequestMapping(value = "accounts")

public class AccountController {

 

    @GetMapping(value = "/{accountId}")

    public Account getAccountById(@PathVariable("accountId") Long accountId) {

        Account account = new Account();

        account.setId(1L);

        account.setAccountCode("DemoCode");

        account.setAccountName("DemoName");

        return account;

    }

}

In this controller, we have implemented the business process of retrieving user account information based on the account ID (accountId) using static business code.

Two layers of mapping are used here: the first layer is the @RequestMapping annotation, which defines the root path of the service at the service level ("/accounts"); the second layer is the @GetMapping annotation, which defines the specific path and parameter information of the HTTP request method at the operation level.

At this point, a typical RESTful service has been developed, and we can now run the Spring Boot application directly using the java -jar command.

In the startup log, we can see the following output (some content has been adjusted for display purposes), which indicates that the custom AccountController has started successfully and is ready to receive requests.

RequestMappingHandlerMapping: Mapped "{[/accounts/{accountId}], methods=[GET]}" onto public com.springcss.account.domain.Account com.springcss.account.controller.AccountController.getAccountById(java.lang.Long)

In this course, we will use Postman to demonstrate how to access remote services through HTTP endpoints.

Postman provides a visual interface for performing HTTP requests and responses. You can try writing an AccountController and access the endpoint “http://localhost:8082/accounts/1” using Postman to get the response result.

In the previous AccountController, we also saw a new annotation, @PathVariable, which is applied to the input parameter. Now let’s see how these annotations control the input of a request.

Controlling Request Input and Output #

Spring Boot provides a series of useful annotations to simplify the process of controlling request inputs. The commonly used ones include @PathVariable, @RequestParam, and @RequestBody.

The @PathVariable annotation is used to retrieve path parameters, that is, to get the value of the {id} parameter from a path of the form “url/{id}”. The definition of this annotation is shown in the following code:

@Target(ElementType.PARAMETER)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface PathVariable {

    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;
}

Usually, when using the @PathVariable annotation, we only need to specify the name of one parameter. Let’s take a look at another example, as shown in the following code:

@GetMapping(value = "/{accountName}")
public Account getAccountByAccountName(@PathVariable("accountName") String accountName) {
    Account account = accountService.getAccountByAccountName(accountName);
    return account;
}

The @RequestParam annotation is similar to the @PathVariable annotation, and it is also used to get parameters from the request. However, it is used for paths in the format of url?id=XXX.

The definition of this annotation is shown in the following code. Compared to the @PathVariable annotation, it only has an additional defaultValue attribute for setting default values.

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;

    String defaultValue() default ValueConstants.DEFAULT_NONE;
}

In the HTTP protocol, the content-type attribute is used to specify the content type of the transmission. We can set this attribute using the produces attribute in the @RequestMapping annotation.

When setting this attribute, we usually set it to “application/json”, as shown in the following code:

@RestController
@RequestMapping(value = "accounts", produces="application/json")
public class AccountController {

The @RequestBody annotation is used to handle the encoded content when the content-type is of type application/json. With the @RequestBody annotation, we can bind the JSON string in the request body to the corresponding JavaBean.

The following code is an example of using the @RequestBody annotation to control the input:

@PutMapping(value = "/")
public void updateAccount(@RequestBody Account account) {

If we use the @RequestBody annotation, we can enter a JSON string in Postman to construct the input object, as shown in the following code:

Drawing 1.png

Example of using Postman to input a JSON string and make an HTTP request

From the above explanation, we can see that using annotations is very simple. Next, it is necessary for us to discuss the rules for controlling request inputs.

Regarding the rules for controlling request inputs, the key is to design the HTTP endpoints in accordance with RESTful design principles. There are also some conventions in the industry regarding this.

  • Taking the Account domain entity as an example, if we consider it as a resource, the root node of the HTTP endpoint is usually named in plural form, i.e., “/accounts”, as shown in the previous example code.
  • When designing RESTful APIs, we need to design the detailed paths of the endpoints exposed to the outside world based on the HTTP semantics. For common CRUD operations, we show some differences between RESTful APIs and non-RESTful APIs.

图片3

Comparison of RESTful style examples

Based on the implementation methods of controlling request inputs described above, we can provide the complete implementation process of the AccountController class in the account-service, as shown in the following code:

@RestController
@RequestMapping(value = "accounts", produces="application/json")
public class AccountController {

    @Autowired
    private AccountService accountService;

    @GetMapping(value = "/{accountId}")
    public Account getAccountById(@PathVariable("accountId") Long accountId) {
        Account account = accountService.getAccountById(accountId);
        return account;
    }

    @GetMapping(value = "accountname/{accountName}")
    public Account getAccountByAccountName(@PathVariable("accountName") String accountName) {
        Account account = accountService.getAccountByAccountName(accountName);
        return account;
    }

    @PostMapping(value = "/")
    public void addAccount(@RequestBody Account account) {
        accountService.addAccount(account);
    }

    @PutMapping(value = "/")
    public void updateAccount(@RequestBody Account account) {
        accountService.updateAccount(account);
    }

    @DeleteMapping(value = "/")
    public void deleteAccount(@RequestBody Account account) {
        accountService.deleteAccount(account);
    }

}

After introducing the control of request inputs, let’s discuss how to control the output of requests.

Compared to input control, output control is much simpler because the @RestController annotation provided by Spring Boot shields the complexity of the underlying implementation. We only need to return a normal business object. The @RestController annotation is equivalent to the combination of the @Controller and @ResponseBody annotations in Spring MVC, and they will automatically return JSON data.

Here, we also provide the implementation process of the OrderController class in the order-service, as shown in the following code:

@RestController
@RequestMapping(value="orders/jpa")
public class JpaOrderController {

    @Autowired
    JpaOrderService jpaOrderService;

    @GetMapping(value = "/{orderId}")
    public JpaOrder getOrderById(@PathVariable Long orderId) {
        JpaOrder order = jpaOrderService.getOrderById(orderId);
        return order;
    }

    @GetMapping(value = "orderNumber/{orderNumber}")
    public JpaOrder getOrderByOrderNumber(@PathVariable String orderNumber) {
        JpaOrder order = jpaOrderService.getOrderByOrderNumber(orderNumber);
        //JpaOrder order = jpaOrderService.getOrderByOrderNumberByExample(orderNumber);
        //JpaOrder order = jpaOrderService.getOrderByOrderNumberBySpecification(orderNumber);
        return order;
    }

    @PostMapping(value = "")
    public JpaOrder addOrder(@RequestBody JpaOrder order) {
        JpaOrder result = jpaOrderService.addOrder(order);
        return result;
    }

}

From the above example, we can see that we used Spring Data JPA from Lesson 09 to complete the implementation of entity objects and data access functionality.

Summary and Preview #

Building web services is a basic requirement for developing web applications, and designing and implementing RESTful web services is a necessary development skill for developers.

With the Spring Boot framework, developers only need to use a few annotations to achieve complex HTTP endpoints and expose them to other services for use. The work becomes very simple.