Tuesday 16 July 2019

Item Service - Building a RESTful Service with Spring Boot

In this post we will build a microservice using Spring Boot.  This service will be responsible for interacting with our items that we are storing in our MySQL database.


Layered Architecture

Spring Boot has been built with layered architectures in mind.  In a layered architecture, the components within a specify layer only deal with logic that pertains to that layer. This gives us a clear separation of concerns.  Also a layer may only speak to its adjacent layers, this makes it much easier for us to manage dependencies.  Dependencies between layers will be handled by leveraging dependency injection.  The real value of this will become apparent in an upcoming post on testing the individual layers.

Domain Model

We will leverage the Java Persistence API (JPA) to make interacting with the database easier.  Spring Boot uses Hibernate as the default implementation, but the code below would be the same if you were using the TopLink/EclipseLink implementations.
package cloudshop.itemservice.entity;

import javax.persistence.Entity;
import javax.persistence.GenerationType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Objects;

@Entity
@Table(name="items")
public class Item {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;
    private double price;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
    
    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public boolean equals(Object obj) {
        try {
            if(null == obj) {
                return false;
            }
            Item item = (Item) obj;
            if(!Objects.equals(this.id, item.getId())) {
                return false;
            }
            if(!Objects.equals(this.name, item.getName())) {
                return false;
            }
            if(!Objects.equals(this.description, item.getDescription())) {
                return false;
            }
            if(!Objects.equals(this.price, item.getPrice())) {
                return false;
            }
            return true;
        } catch (ClassCastException e) {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return Long.hashCode(id);
    }

}

Repository Layer

A Spring Boot repository is an abstraction to reduce the amount of boiler plate code required to implement a data access layer. Since we are using JPA, we can implement this quite easily by extending CrudRepository and specifying our entity class (Item) and the primary key type (Long).
package cloudshop.itemservice.repository;

import cloudshop.itemservice.entity.Item;
import org.springframework.data.repository.CrudRepository;

// This will be AUTO IMPLEMENTED by Spring into a Bean called itemRepository
// CRUD refers Create, Read, Update, Delete
public interface ItemRepository extends CrudRepository<Item, Long> {

}

Service Layer

The service layer is where we put the business logic that will interact with the repository layer.  We idenitfy this class as a service using the @Service annotation.  We will leverage Spring Boot's dependency injection to gain access to our repository instead of configuring it directly.
package cloudshop.itemservice.service;

import cloudshop.itemservice.entity.Item;
import cloudshop.itemservice.repository.ItemRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.*;

import java.util.Optional;

@Service
public class ItemService {

    @Autowired
    private ItemRepository itemRepository;

    public Iterable<Item> getAllItems() {
        return itemRepository.findAll();
    }

    public Optional<Item> getItem(@PathVariable long id) {
        return itemRepository.findById(id);
    }

    public Item createItem(@RequestBody Item item) {
        return itemRepository.save(item);
    }

    public Optional<Item> updateItem(Item item) {
        Optional<Item> itemOptional = itemRepository.findById(item.getId());
        if (!itemOptional.isPresent()) {
            return itemOptional;
        }
        Item updatedItem = itemRepository.save(item);
        return Optional.of(updatedItem);
    }

    public Optional<Item> deleteItem(long id) {
        Optional<Item> itemOptional = itemRepository.findById(id);
        if(itemOptional.isPresent()) {
            itemRepository.delete(itemOptional.get());
        }
        return itemOptional;
    }

}

 REST Layer

The controller will interact with REST calls and then make the appropriate calls to the service layer.  We idenitfy this class as a service using the @Controller annotation.  We will leverage Spring Boot's dependency injection to gain access to our service instead of configuring it directly.
package cloudshop.itemservice.controller;

import cloudshop.itemservice.entity.Item;
import cloudshop.itemservice.service.ItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.net.URI;
import java.util.Optional;

@Controller
@RequestMapping(path="/items")
public class ItemController {

    @Autowired
    private ItemService itemService;

    @GetMapping
    public @ResponseBody Iterable<Item> getAllItems() {
        return itemService.getAllItems();
    }

    @GetMapping(value = "/{id}")
    public @ResponseBody ResponseEntity<?> getItem(@PathVariable long id) {
        Optional<Item> itemOptional = itemService.getItem(id);
        if(!itemOptional.isPresent()) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(itemOptional.get());
    }

    @PostMapping
    public ResponseEntity<?> createItem(@RequestBody Item item) {
        Item savedItem = itemService.createItem(item);

        URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
                .buildAndExpand(savedItem.getId()).toUri();

        return ResponseEntity.created(location).build();
    }

    @PutMapping("/{id}")
    public ResponseEntity<?> updateItem(@RequestBody Item item, @PathVariable long id) {
        item.setId(id);
        Optional<Item> studentOptional = itemService.updateItem(item);
        if (!studentOptional.isPresent()) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.noContent().build();
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<?> deleteItem(@PathVariable long id) {
        Optional<Item> itemOptional = itemService.deleteItem(id);
        if(!itemOptional.isPresent()) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.noContent().build();
    }

}

Application

Now we will implement an Application class to make our Spring Boot application runnable.

package cloudshop.itemservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

Configuration 

Below are the config files we need to make our application work.

Maven (pom.xml)

We will be leveraging Maven to make the management of our project a little easier.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bdoughan</groupId>
    <artifactId>item-service</artifactId>
    <version>0.1.0</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- JPA Data (We are going to use Repositories, Entities, Hibernate, etc...) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- Use MySQL Connector-J -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


Spring Boot

The Item Service is still in the development stage, but we intend to take in to production.  The production environment will have different configuration than our development environment.  To handle this we can utilize Spring Boot profiles.

Common Properties (application.properties)

Any common configuration can be put in a file called "application.properties". Below we are stating that Hibernate should not create anything in the database for us based on the JPA metadata.
spring.jpa.hibernate.ddl-auto=none

Dev Profile Properties (application-dev.properties)

The database connection metadata will definitely be different between development and production so we will specify a properties file for a dev profile.  The naming convention for this file is "application-{profile}.properties".
spring.datasource.url=jdbc:mysql://localhost:3306/cloudShopDb
spring.datasource.username=item_service_user
spring.datasource.password=item_service_password

Running the Application

We will use Maven to run our Spring Boot Application.  Since we are using profiles, we must remember to specify the profile name.

mvn spring-boot:run -Dspring-boot.run.profiles=dev

Making the REST Calls

Now that our service is running we can use curl commands (or your favourite REST client) to try it out.

Get All Items

Here we see the items corresponding to the data we used to prepopulate our database.

curl -X GET http://localhost:8080/items
[{"id":1,"name":"apple","description":"A red apple","price":1.19},{"id":2,"name":"banana","description":"A yellow banana","price":0.87}]

Create an Item

We will use a POST command to create a new item.  We do not specify an id since one will be generated for us.  The URI of the newly created item is returned to us in the "Location" header.  To see the response headers we made the call with the "-i" flag.

curl -X POST -i -H "Content-Type: application/json"  -d "{\"name\":\"carrot\", \"description\":\"An orange carrot\", \"price\":0.99}"  http://localhost:8080/items
HTTP/1.1 201
Location: http://localhost:8080/items/3
Content-Length: 0
Date: Tue, 16 Jul 2019 14:23:24 GMT

Read One Item

Now we can do a GET with the URI returned from the POST call to see our newly created item.  We can also see that an id was generated for it.

curl -X GET http://localhost:8080/items/3
{"id":3,"name":"carrot","description":"An orange carrot","price":0.99}

Update an Item

A PUT call can be used to update the item.

curl -X PUT -i -H "Content-Type: application/json"  -d "{\"id\":3,\"name\":\"carrot\", \"description\":\"A healthy orange carrot\", \"price\":0.99}"  http://localhost:8080/items/3
HTTP/1.1 204
Date: Tue, 16 Jul 2019 14:26:07 GMT

Delete an Item

Finally a DELETE call can be made to remove an item.

curl -X DELETE -i http://localhost:8080/items/3
HTTP/1.1 204
Date: Tue, 16 Jul 2019 14:26:44 GMT

Useful Links

Below are some links that I found helpful when creating this post:
  1. Accessing Data with MySQL
    https://spring.io/guides/gs/accessing-data-mysql/
  2. Creating a CRUD REST API/Service with Spring Boot, JPA and Hibernate
    https://www.springboottutorial.com/spring-boot-crud-rest-service-with-jpa-hibernate
  3. Layered Architecture & Spring Boot
    https://medium.com/@RogelioOrts/layered-architecture-spring-boot-af7dc071d2b5


No comments:

Post a Comment