10 February 2018

Pure Restful web services with Spring Data Rest

Last couple of weeks I had a chance to work with spring data rest project. When I read the documentation I thought I pretty awesome.Because It has lot of features for REST api development.Here I will describe main features contains in spring data rest. At the end of this thread I will create simple rest api for product management.

What is Pure Restful Webservice ?

First we talk about what is restful web service.  Restful web service is architectural style for request and response mapping in web service. There are different kind of HTTP request coming to API. But when it comes to REST api every url have different meaning for different HTTP request methiods.

Ex: /api/v1/student/  --- > GET
      /api/v1/student/ ----> POST
     /api/v1/student/ ---> PUT

And there are different response Status codes for different purposes.
Method Method
200 OK 201 Created
202 Accepted 203 Non-Authoritative Info
204 No content 205 Reset content
206 Partial content
300 Multiple choice 301 Moved permanently
302 Found 303 See other
304 Not modified 306 (unused)
307 Temporary redirect
400 Bad request 401 Unauthorized
402 Payment required 403 Forbidden
404 Not found 405 Method not allowed
406 Not acceptable 407 Proxy auth required
408 Timeout 409 Conflict
410 Gone 411 Length required
412 Preconditions failed 413 Request entity too large
414 Requested URI too long 415 Unsupported media
416 Bad request range 417 Expectation failed
500 Server error 501 Not implemented
502 Bad gateway 503 Service unavailable
504 Gateway timeout 505 Bad HTTP version

As Example When we POST student obejct to rest API it will need to Send 204 no content status code rather than 200 status code.likewise there are standard ways to send response.If all the standard are fulfill  it will become pure restful web service.

These are the core areas we are discus in this tutorial.
  1. Projection
  2. Search
  3. Advance Search
  4. Pagination & Sorting
  5. Spring Security
To Discuss spring data rest we used simple sales management demo. These are the entity classes. We have Product,Sale,SalesAgent

package dev.firelimez.io.domain;


import org.springframework.data.annotation.Id;


public class Product {
    @Id
    private String productId;
    private String name;
    private String price;

    public String getProductId() {
        return productId;
    }

    public void setProductId(String productId) {
        this.productId = productId;
    }

    public String getName() {
        return name;
    }

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

    public String getPrice() {
        return price;
    }

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



package dev.firelimez.io.domain;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.DBRef;

import java.util.List;

public class Sale {
    @Id
    private String salesId;
    private String date;

    @DBRef
    private SalesAgent salesAgent;

    @DBRef
    private List<Product> products;

    public String getSalesId() {
        return salesId;
    }

    public void setSalesId(String salesId) {
        this.salesId = salesId;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public List<Product> getProducts() {
        return products;
    }

    public void setProducts(List<Product> products) {
        this.products = products;
    }

    public SalesAgent getSalesAgent() {
        return salesAgent;
    }

    public void setSalesAgent(SalesAgent salesAgent) {
        this.salesAgent = salesAgent;
    }
}

package dev.firelimez.io.domain;


import org.springframework.data.annotation.Id;

import java.io.Serializable;

public class SalesAgent implements Serializable {

    @Id
    private String agentId;
    private String name;
    private String lastName;
    private String age;

    public String getName() {
        return name;
    }

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

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getAgentId() {
        return agentId;
    }

    public void setAgentId(String agentId) {
        this.agentId = agentId;
    }
}

So this tutorial I will use mongo database as database reference. First you have to add these dependencies to your maven project.


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

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.2</version>
        </dependency>
    </dependencies>

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

Spring data rest all are written using interfaces. So first thing we have to do is create repository. All the entity classes should have repository classes. As example this is the example Repository class.


package dev.firelimez.io.repo;

import dev.firelimez.io.domain.Product;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;

import java.util.List;

@RepositoryRestResource(collectionResourceRel = "products", path = "products")
public interface ProductRepository extends MongoRepository<Product, String> {

    @RestResource(path = "names", rel = "demo1")
    List<Product> findByName(@Param("name") String name);


    @RestResource(path = "similar", rel = "demo")
    List<Product> findByNameLike(@Param("name") String name);

    @Override
    @RestResource(exported = true)
    void delete(String productId);
}

And Final step is start program as spring boot application.

package dev.firelimez.io;

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

@SpringBootApplication
public class StarterApplication {

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

And next step is add application.yml and configuration should be like this.

server:
    port: 9008

spring:
 data:
    mongodb:
      host: 127.0.0.1
      port: 27017
      database: sales_management

And that is your REST API  is ready.


Projection

Projection is concept expose specific fields to rest api end points. As example in sales rest API  how to get the sales agent details who are done the  sales. In this case we can use projection. For that we have to add what are the properties we expect when we using projection. 

Ex:  sales expand projection return sales agent details with the sales objects.


package dev.firelimez.io.domain.projection;

import dev.firelimez.io.domain.Sale;
import org.springframework.data.rest.core.config.Projection;

import java.util.List;

@Projection(name = "expand", types = Sale.class)
interface SalesProjection {

    String getSalesId();

    String getDate();

    SalesAgentProjection getSalesAgent();

    List<ProductProjection> getProducts();
}


package dev.firelimez.io.domain.projection;

import org.springframework.beans.factory.annotation.Value;

interface SalesAgentProjection {

    int getAge();

    String getAgentId();

    @Value("#{target.name} #{target.lastName}")
    String getFullName();
}


And then you can access projection data using this way.


http://localhost:9008/sales?projection=expand


Search & Advance Search

When we creating rest api biggest issue is dealing with database and based on user REST end point what we did was write query and get result from database and return to user. But spring data rest support queryless way to get result from database. 

Ex:  if we want to get product by product name 


@RestResource(path = "names", rel = "name")
    List<Product> findByName(@Param("name") String name);

These are the inbuild query methods in spring jpa repository.

Advanced Search

For advance search we have to use ExampleMatcher class and it will support advance search in spring data rest.


    @Autowired
    SalesAgentRepository salesAgentRepository;

    @RequestMapping(method = RequestMethod.POST, path = "/api/v2/agent/search/advance", consumes = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public List<SalesAgent> claimSupportTicket(@RequestBody SalesAgent salesAgent) {
        ExampleMatcher exampleMatcher = ExampleMatcher.matching().withIgnoreNullValues().withIgnoreCase();
        List<SalesAgent> advanceSearch = salesAgentRepository.findAll(Example.of(salesAgent, exampleMatcher));
        return advanceSearch;
    }

No comments:

Post a Comment