Web API Best Practices - SDD...

Preview:

Citation preview

Chander Dhall

Twitter @csdhall

Me@ChanderDhall.com

Web API Best Practices

About me• Microsoft MVP

• Asp.NET Insider

• Web API Advisor

• Pluralsight contributor

• Dev Chair – DevConnections

• Conference Organizer – MVPMIX.com

• Conference Organizer - jsSaturday

• Leader – Rockstar Developers Meetup Austin

• Leader – .NET user group at UTDallas

• President– Chander Dhall, Inc.

Agenda• REST Principles

• Web API – Best Practices

• A Typical Web API Pipeline

3

• Participation == :-)

• No participation == :-(

ChanderDhall.com/Podcasts

Free Resharper

• http://ChanderDhall.com/CodeCamp

Step 1

Appserver & DBServer

Database ServerApp Server

Awesome Solution?

Load BalancerLoad

Balancer

DB

DB

DB

DB Cluster

Has

h Ma

p

DB

DB

DB

DB Cluster

Load BalancerLoad

Balancer

DB

DB

DB

DB Cluster

Has

h Ma

p

DB

DB

DB

DB Cluster

Set 1-10 Million Users Set 11-20 Million Users

Global Redirector

Global

Look up Hash

Map

May be close to an awesome

solution?Load Balancer

Load

Balancer

DB

DB

DB

DB Cluster

Hash

Map

DB

DB

DB

DB Cluster

Master

Slave Slave

SANNo Sql

Master

Slave Slave

Search Db

CachingOffline

Processing

Why?

• Amazon claim – Just an extra 1/10th of a second

on their response times will cost them 1% in sales.

• Google – ½ a second increase in latency caused

traffic to drop by a fifth.

1. REST Principles

Principle 1 - Client-server● Clients are not concerned with data storage,

which remains internal to each server.

● Servers are not concerned with the user

interface or user state.

● Servers and clients may also be replaced

and developed independently, as long as

the interface between them is not altered.

1. REST Principles

Principle 2 - Stateless● The client–server communication is further

constrained by no client context being

stored on the server between requests.

No sql paradigm – Materialized

pathsElectronics

TV Phones Computers Cameras

Samsung Apple LG

LCD LED

No sql paradigm – Materialized

pathsTV

Samsung Apple LG

LCD LED

{ “entity”: “TV”,

“category”:”Electronics”}{ “entity”: “Samsung”, “category”:”Electronics, TV”}

{ “entity”: “Samsung”, “category”:”Electronics, TV, LCD”}

1. REST Principles

Principle 3 - Cacheable● Clients can cache responses.● Responses must therefore, implicitly or

explicitly, define themselves as cacheable, or not, to prevent clients reusing stale or inappropriate data in response to further requests.

● Well-managed caching partially or completely eliminates some client–server interactions, further improving scalability and performance.

1. REST Principles

Principle 4 - Layered system● A client cannot ordinarily tell whether it is

connected directly to the end server, or to

an intermediary along the way.

● Intermediary servers may improve system

scalability by enabling load-balancing and

by providing shared caches.

No sql paradigm – Nested sets

Electronics

TV Phones

Samsung Sony Cell Landline1 2 3 4 5 6 7 8 9 10 11 12 13 14

1. REST Principles

Principle 5 - Code on demand● Servers are able temporarily to extend or

customize the functionality of a client by

the transfer of executable code.

1. REST Principles

Principle 6 - Uniform Interface● The uniform interface between clients and

servers, discussed below, simplifies and

decouples the architecture, which enables

each part to evolve independently.

http://ChanderDhall.com/gettrainings

GET

http://ChanderDhall.com/trainings

Principle: Use HTTP verbs

20

Nouns, No Verbs in the URL

What about?

• Responses that don’t involve resources

• Use verbs

• Example:

http://ChanderDhall.com/translate?from=en

&to=gn

http://ChanderDhall.com/trainings

http://ChanderDhall.com/trainings/34

Principle: 2 base

URLs/resource

22

KISS (Keep it simple, stupid!)

• HTTP Verbs

GET

http://ChanderDhall.com/trainings

Retrieves all trainings

GET

http://ChanderDhall.com/trainings/23

Retrieves trainings with Id = 2323

GET

• HTTP Verbs

POST

http://ChanderDhall.com/trainings

Creates a new training(Strange?)

POST

http://ChanderDhall.com/trainings/23

Error – Why, so?

24

POST

• HTTP Verbs

PUT

http://ChanderDhall.com/trainings

Bulk update trainings

PUT

http://ChanderDhall.com/trainings/23

If exists, update 23

Else ERROR

25

PUT

• HTTP Verbs

DELETE

http://ChanderDhall.com/trainings

Delete all trainings

DELETE

http://ChanderDhall.com/trainings/23

If exists, Delete 23

Else Resource Not found Error

26

DELETE

Associations

GET

/trainers/12/trainings/144

27

GET

/trainers/12/trainings?zip=926

18&tech=api

28

Complexity

Error Format

• Error format 1

{

“code" : "401",

"message“ : "Authenticate",

}

29

Error Format

• Error format 2

{

“type" : “OAuthException",

"message“ : “(#401: Request not authorized",

}

30

Error Format

• Error format 3

{

"status" : "401",

"message“ : "Authenticate",

"code“ : 1234,

“details":

"http://www.chanderdhall.com/docs/errors/1234"

}

31

(Recommended!)

Error Format

• Google GData 200 201 304 400 401 403

404 409 410 500

• Netflix 200 201 304 400 401 403 404 412

500

• Digg 200 400 401 403 404 410 500 503

Errors

• Only 3 cases

• Everything worked

• Client did something wrong

• API did something wrong

33

Error Codes (Required)

• So minimum response codes

• 200 - OK

• 400 - Bad Request

• 500 - Internal Server Error

34

Error Codes (Extended)

• 201 - Created

• 304 - Not Modified

• 404 – Not Found

• 401 - Unauthorized

• 403 - Forbidden

35

Versioning

• /bankName/v2.0/accounts/checking

• /2013-09-10/bankName/accounts/checkingThis

• /bankName/accounts/checking?v=1

36

Versioning

• Timestamp: Is the version based on the

date of launch or date of creation?

• Version in URL: Interface appears to

change sooner that it actually might.

• Version is optional: (Red Flag)

37

Versioning

• Versioning is mandatory

• Make version the first field if you want

from the left (v1/trainings)

• How many versions to maintain?

• URLs or Headers?

38

Versioning

• Accept-Datetime: Thu, 1 Oct 2013

20:35:00 GMT

• Cookie: $Version=1; Skin=new;

• ETag:

"737060cd8c284d8af7ad3082f209582d"

39

Pagination

• http://chanderdhall.com/podcasts?page

=3&rpp=5

• http://chanderdhall.com/podcasts?start=

3&count=5

40

Pagination

• http://chanderdhall.com/podcasts?offset

=3&limit=5

• Don’t forget to include Default Pagination

41

Multiple Formats

• http://chanderdhall.com/podcasts?type=json

Or

http://chanderdhall.com/podcasts?type=xml

• http://chanderdhall.com/podcasts.json

Or http://chanderdhall.com/podcasts.xml

42

Multiple Formats

• http://chanderdhall.com/podcasts

accept: application/json

43

Responses

a. "Created_At": "2013-10-10T04:35:00Z"

b. "DateTime": "2013-10-10T04:35:00Z"

44

Responses

var podcast = JSON.parse(response);

podcast.createdAt

45

P

I

P

E

L

I

N

E

W

E

B

A

P

I

ASP.NET Hosting Self Hosting

HttpControllerHandler

HttpServer HttpSelfHostServer

DelegatingHandler

HttpRoutingDispatcher

HttpControllerDispatcher

HttpRequestMessage HttpResponse

Message

If(Route.Handler) Yes Route.Handler

DelegatingHandler

HttpMessageHandler

No

1. Create API Controller

Message Handlers

W

E

B

A

P

I

Action Filters

2. Select Controller ActionP

I

P

E

L

I

N

E

Controller

Can create an error response if request is not

authorized3. Model Binding

Authorization Filters

Action filters are invoked

twice

4. Result Conversion

5. Invoke Action

OnActionExecuting OnActionExecuted

Controller Action

Exception Filters

3. Model Binding

HttpRequestMessage

URI Headers Body

IModelBinder

ModelBinderParameterBinding

IValueProvider

Simple Type Any Type

HttpParameterBinding

Complex Type

MediaTypeFormatter

FormatterParameterBinding

4. Result Conversion

HttpResponseMessage

void

If(returnType== void)

return (204)

HttpResponseMessage

If(returnType == HttpResponseMessage)

Pass through

Other Types

IContentNegotiator

MediaTypeFormatter

Features you should know about

• Documentation

Features you should know about

• Test Client (Nuget)

• Install-Package WebApiTestClient

Features you should know about

• External Authentication Services

● Microsoft Accounts.

● Twitter

● Facebook

● Google

• And more

Features you should know about

• Unit Testing

Features you should know about

• Enable Cross Origin Requests

Install-Package

Microsoft.AspNet.WebApi.Cors -pre -

project WebService

Enable Cross Origin Requests

public static class WebApiConfig

{

public static void Register(HttpConfiguration config)

{

var cors = new

EnableCorsAttribute("www.example.com", "*", “*");

config.EnableCors(cors);

// ...

}

}

Behind the scenes

Access-Control-Allow-Origin: http://www.example.com

Access-Control-Allow-Headers: x-my-custom-header

Access-Control-Allow-Methods: PUT

Enable CORS (Method Level)

public class ItemsController : ApiController

{

public HttpResponseMessage GetAll() { ... }

[EnableCors(origins: "http://www.example.com",

headers: "*", methods: "*")]

public HttpResponseMessage GetItem(int id) { ... }

public HttpResponseMessage Post() { ... }

public HttpResponseMessage PutItem(int id) { ... }

}

Enable CORS (Controller level)

[EnableCors(origins: "http://www.example.com", headers:

"*", methods: "*")]

public class ItemsController : ApiController

{

public HttpResponseMessage GetAll() { ... }

public HttpResponseMessage GetItem(int id) { ... }

public HttpResponseMessage Post() { ... }

[DisableCors]

public HttpResponseMessage PutItem(int id) { ... }

}

Enable CORS (Custom)public class MyCorsPolicyAttribute : Attribute, ICorsPolicyProvider{

private CorsPolicy _policy;

public MyCorsPolicyAttribute(){

// Create a CORS policy._policy = new CorsPolicy{

AllowAnyMethod = true,AllowAnyHeader = true

};

// Add allowed origins._policy.Origins.Add("http://myclient.azurewebsites.net");_policy.Origins.Add("http://www.contoso.com");

}

public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request)

{return Task.FromResult(_policy);

}}

Web API 2 (New Features)

• Attribute Routing

Attribute Routing

[Route("api/books")]

public IEnumerable<Book> GetBooks() { ... }

[Route("api/books/{id:int}")]

public Book GetBook(int id) { ... }

[Route("api/books")]

public HttpResponseMessage CreateBook(Book

book) { ... }

Attribute Routing

[RoutePrefix("api/books")]

public class BooksController : ApiController

{

// GET api/books

[Route("")]

public IEnumerable<Book> Get() { ... }

// GET api/books/5

[Route("{id:int}")]

public Book Get(int id) { ... }

// POST api/books

[Route("")]

public HttpResponseMessage Post(Book book) { ... }

}

Attribute Routing

[RoutePrefix("api/books")]

public class BooksController : ApiController

{

// GET /api/authors/1/books

[Route("~/api/authors/{authorId:int}/books")]

public IEnumerable<Book> GetByAuthor(int authorId) {

... }

// ...

}

‘~’ Overrides the RoutePrefix

Attribute Routing

[RoutePrefix("customers/{customerId}")]

public class OrdersController : ApiController

{

// GET customers/1/orders

[Route("orders")]

public IEnumerable<Order> Get(int customerId) { ... }

}

Attribute Routing

[Route("users/{id:int}"]

public User GetUserById(int id) { ... }

[Route("users/{name}"]

public User GetUserByName(string name) { ... }

First route chosen if the id is an int.

Otherwise, second.

Constraint Description Example

alphaMatches uppercase or lowercase Latin

alphabet characters (a-z, A-Z){x:alpha}

bool Matches a Boolean value. {x:bool}

datetime Matches a DateTime value. {x:datetime}

decimal Matches a decimal value. {x:decimal}

double Matches a 64-bit floating-point value. {x:double}

float Matches a 32-bit floating-point value. {x:float}

guid Matches a GUID value. {x:guid}

int Matches a 32-bit integer value. {x:int}

lengthMatches a string with the specified length or

within a specified range of lengths.

{x:length(6)}

{x:length(1,20)}

long Matches a 64-bit integer value. {x:long}

max Matches an integer with a maximum value. {x:max(10)}

maxlength Matches a string with a maximum length. {x:maxlength(10)}

min Matches an integer with a minimum value. {x:min(10)}

minlength Matches a string with a minimum length. {x:minlength(10)}

range Matches an integer within a range of values. {x:range(10,50)}

regex Matches a regular expression. {x:(^\d{3}-\d{3}-\d{4}$)}

Attribute Routing

[Route("users/{id:int:min(1), id:int:max(10)}")]

public User GetUserById(int id) { ... }

Multiple constraints can be applied separated by a

comma

No sql paradigm – Index table

Employee Id Details

1234 Email: a@a.com; State: CA; Dept: IT

8235 Email: b@b.com; State: TX; Dept: Sales

2234 Email: c@c.com; State: AL; Dept: IT

1671 Email: c@d.com; State: WA; Dept: Sales

State Employee Id

CA 1234, 1235, 1236, 1244

TX 8000, 8100, 8235, 8266

AL 2212, 2221, 2234, 2256

Dept Employee Id

IT 1234, 1235, 1236, 1244

Sales 8000, 8100, 8235, 8266

Acc 2212, 2221, 2234, 2256

No sql paradigm – Tree Index

Country - USA

State - CA

City - LA

Properties

Facilities

{

“property”:

[{ “facilityName”:

“abc”,

“facilityId”:”111”},

{“facilityName”:”

xyz” ,

“facilityId”:”222”}]

}

No sql paradigm – Composite Key

Dept= IT:* or

Dept= Sales:Online:*

IT: Software: 1123 EmpName: John; Address: Los Angeles

IT: Software: 2323 EmpName: Kevin; Address: Dallas, TX

IT: Hardware: 6767 EmpName: Matt; Address: San Francisco

Sales: Online: 832 EmpName: Katie: Address: Austin, Tx

Sales : Online: 423 EmpName: Karen: Address: Irvine, CA

Sales : Store : 556 EmpName: Richard; Address: San Diego

IT

Employees

Sales

Employ

ees

E

M

P

L

O

Y

E

E

S

No sql paradigm - Grouping

U123: O111Product Ids: [“Surface”,

“xbox”]U124:O123 Product Ids: [“Win 8”, “xbox”]

U124:O234 Product Ids: [“Win phone”, “surface”]

U124:O999Product Ids: [“office”, “azure

sub”]U125:O789

Product Ids: [“msdn”,

“office”]U125:O945

Product Ids: [“surface”,

“xbox”]

Colocation of a

users’ data.

GroupBy clause

No sql paradigm – Inverted search

& direct aggregation

EmpId, dept, city, …….

Dept-IT: [111, 123, 234….]

Dept-Sales:[673, 343, 434….]

City: Dallas

City: LA

111: Dept-Sales, City: LA …

222: Dept-IT, City: Dallas ….

No sql paradigm – Materialized

pathsElectronics

TV Phones Computers Cameras

Samsung Apple LG

LCD LED

No sql paradigm – Materialized

pathsTV

Samsung Apple LG

LCD LED

{ “entity”: “TV”,

“category”:”Electronics”}{ “entity”: “Samsung”, “category”:”Electronics, TV”}

{ “entity”: “Samsung”, “category”:”Electronics, TV, LCD”}

No sql paradigm – Nested sets

Electronics

TV Phones

Samsung Sony Cell Landline1 2 3 4 5 6 7 8 9 10 11 12 13 14

No sql paradigm – Nested sets

1 2 3 4 5 6 7 8 9 10 11 12 13

SonySamsung

TV

LandlineCell

Phone

Electronics

No sql paradigm : Flattening nested

documents

Name:

Chander

Hadoop: Expert

Nodejs: Expert

Spanish: Novice

{

“name”:”chander”,

“skills”:”hadoop, nodejs,

Spanish”,

“level”:”expert, expert,

novice”

}

Skills:hadoop AND

level:expert

No sql paradigm : Flattening nested

documents

Name: Chander

Hadoop: Expert

Nodejs: Expert

Spanish: Novice

{“name”:”chander”,“skills_1”:”hadoop”,“skills_2”: “nodejs”,“skills_3”: “spanish”, “level_1”:”expert”,“level_2”: “expert”,“level_3”: “novice”}

Search

78

Formatted results

79

Hypermedia

80

Resources

• http://chanderdhall.com/CodeCamp

• me@ChanderDhall.com

• Twitter @csdhall

• http://apigee.com

Recommended