Skip to content

Latest commit

 

History

History
305 lines (256 loc) · 7.83 KB

File metadata and controls

305 lines (256 loc) · 7.83 KB

RESTful Web Services

Social Media Application Resource Mappings

User -> Posts

  • Retrieve all Users - GET /users

  • Create a User - POST /users

  • Retrieve one User - GET /users/{id} -> /users/1

  • Delete a User - DELETE /users/{id} -> /users/1

  • Retrieve all posts for a User - GET /users/{id}/posts

  • Create a posts for a User - POST /users/{id}/posts

  • Retrieve details of a post - GET /users/{id}/posts/{post_id}

Error in the Log

Resolved exception caused by Handler execution: 
org.springframework.http.converter.HttpMessageNotWritableException: 
No converter found for return value of type: 
class com.in28minutes.rest.webservices.restfulwebservices.HelloWorldBean
  • This happened because there were no getters in HelloWorldBean class

Questions to Answer

  • What is dispatcher servlet?

  • Who is configuring dispatcher servlet?

  • What does dispatcher servlet do?

  • How does the HelloWorldBean object get converted to JSON?

  • Who is configuring the error mapping?

  • Mapping servlet: 'dispatcherServlet' to [/]

  • Mapped "{[/hello-world],methods=[GET]}" onto public java.lang.String com.in28minutes.rest.webservices.restfulwebservices.HelloWorldController.helloWorld()

  • Mapped "{[/hello-world-bean],methods=[GET]}" onto public com.in28minutes.rest.webservices.restfulwebservices.HelloWorldBean com.in28minutes.rest.webservices.restfulwebservices.HelloWorldController.helloWorldBean()

  • Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)

  • Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)

Example Requests

[
    {
        "id": 1,
        "name": "Adam",
        "birthDate": "2017-07-19T04:40:20.796+0000"
    },
    {
        "id": 2,
        "name": "Eve",
        "birthDate": "2017-07-19T04:40:20.796+0000"
    },
    {
        "id": 3,
        "name": "Jack",
        "birthDate": "2017-07-19T04:40:20.796+0000"
    }
]
{
    "id": 1,
    "name": "Adam",
    "birthDate": "2017-07-19T04:40:20.796+0000"
}
{
    "name": "Ranga",
    "birthDate": "2000-07-19T04:29:24.054+0000"
}
  • Get request to a non existing resource.
  • The response shows default error message structure auto configured by Spring Boot.
{
    "timestamp": "2017-07-19T05:28:37.534+0000",
    "status": 404,
    "error": "Not Found",
    "message": "id-500",
    "path": "/users/500"
}
  • Get request to a non existing resource.
  • The response shows a Customized Message Structure
{
    "timestamp": "2017-07-19T05:31:01.961+0000",
    "message": "id-500",
    "details": "Any details you would want to add"
}

POST http://localhost:8080/users with Validation Errors

Request
{
    "name": "R",
    "birthDate": "2000-07-19T04:29:24.054+0000"
}
Response - 400 Bad Request
{
    "timestamp": "2017-07-19T09:00:27.912+0000",
    "message": "Validation Failed",
    "details": "org.springframework.validation.BeanPropertyBindingResult: 1 errors\nField error in object 'user' on field 'name': rejected value [R]; codes [Size.user.name,Size.name,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.name,name]; arguments []; default message [name],2147483647,2]; default message [Name should have atleast 2 characters]"
}
{
    "id": 1,
    "name": "Adam",
    "birthDate": "2017-07-19T09:26:18.337+0000",
    "_links": {
        "all-users": {
            "href": "http://localhost:8080/users"
        }
    }
}

Internationalization

Configuration
  • LocaleResolver
    • Default Locale - Locale.US
  • ResourceBundleMessageSource
Usage
  • Autowire MessageSource
  • @RequestHeader(value = "Accept-Language", required = false) Locale locale
  • messageSource.getMessage("helloWorld.message", null, locale)

XML Representation of Resources

  • Accept application/xml
<List>
    <item>
        <id>2</id>
        <name>Eve</name>
        <birthDate>2017-07-19T10:25:20.450+0000</birthDate>
    </item>
    <item>
        <id>3</id>
        <name>Jack</name>
        <birthDate>2017-07-19T10:25:20.450+0000</birthDate>
    </item>
    <item>
        <id>4</id>
        <name>Ranga</name>
        <birthDate>2017-07-19T10:25:20.450+0000</birthDate>
    </item>
</List>
  • Accept : application/xml
  • Content-Type : application/xml

Request

<item>
        <name>Ranga</name>
        <birthDate>2017-07-19T10:25:20.450+0000</birthDate>
</item>

Response

  • Status - 201 Created

Generating Swagger Documentation

	public static final Contact DEFAULT_CONTACT = new Contact(
			"Ranga Karanam", "http://www.in28minutes.com", "[email protected]");
	
	public static final ApiInfo DEFAULT_API_INFO = new ApiInfo(
			"Awesome API Title", "Awesome API Description", "1.0",
			"urn:tos", DEFAULT_CONTACT, 
			"Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0");

	private static final Set<String> DEFAULT_PRODUCES_AND_CONSUMES = 
			new HashSet<String>(Arrays.asList("application/json",
					"application/xml"));

	@Bean
	public Docket api() {
		return new Docket(DocumentationType.SWAGGER_2)
				.apiInfo(DEFAULT_API_INFO)
				.produces(DEFAULT_PRODUCES_AND_CONSUMES)
				.consumes(DEFAULT_PRODUCES_AND_CONSUMES);
	}

Resource Method description

	@GetMapping("/users/{id}")
	@ApiOperation(value = "Finds Users by id",
    notes = "Also returns a link to retrieve all users with rel - all-users")
	public Resource<User> retrieveUser(@PathVariable int id) {

API Model

@ApiModel(value="User Details", description="Contains all details of a user")
public class User {

	@Size(min=2, message="Name should have atleast 2 characters")
	@ApiModelProperty(notes = "Name should have atleast 2 characters")
	private String name;

	@Past
	@ApiModelProperty(notes = "Birth Date should be in the Past")
	private Date birthDate;

Filtering

Code
@JsonIgnoreProperties(value={"field1"})
public class SomeBean {
	
	private String field1;
	
	@JsonIgnore
	private String field2;
	
	private String field3;
Response
{
    "field3": "value3"
}

Versioning

  • Media type versioning (a.k.a “content negotiation” or “accept header”)
    • GitHub
  • (Custom) headers versioning
    • Microsoft
  • URI Versioning
    • Twitter
  • Request Parameter versioning
    • Amazon
  • Factors
  • URI Pollution
  • Misuse of HTTP Headers
  • Caching
  • Can we execute the request on the browser?
  • API Documentation
  • No Perfect Solution

More

Table Structure

create table user (
id integer not null, 
birth_date timestamp, 
name varchar(255), 
primary key (id)
);

create table post (
id integer not null, 
description varchar(255), 
user_id integer, 
primary key (id)
);

alter table post 
add constraint post_to_user_foreign_key
foreign key (user_id) references user;