introduction to rest.li

55
Rest.li Development Workflow Overview Joe Betz, April 2014

Upload: jpbetz

Post on 01-Nov-2014

4.562 views

Category:

Software


0 download

DESCRIPTION

Rest.li is an opensource REST framework for building RESTful architectures at scale.

TRANSCRIPT

Page 1: Introduction to rest.li

Rest.liDevelopment Workflow Overview

Joe Betz, April 2014

Page 2: Introduction to rest.li

Let’s implement a Simple REST request

Page 3: Introduction to rest.li

Let’s implement a Simple REST request

GET /fortunes/1 HTTP/1.1 Accept: application/json

Request

Page 4: Introduction to rest.li

Let’s implement a Simple REST request

GET /fortunes/1 HTTP/1.1 Accept: application/json

HTTP/1.1 200 OK Content-Type: application/json Content-Length: … !{ "message": "Today’s your lucky day!" }

Request Response

Page 5: Introduction to rest.li

Step 1. Write a Data Schema

Page 6: Introduction to rest.li

Step 1. Write a Data SchemaFortune.pdsc { "name" : "Fortune", "namespace" : "com.example", "type" : "record", "fields" : [ { "name" : "message", "type" : "string" } ] }

Page 7: Introduction to rest.li

Step 1. Write a Data SchemaFortune.pdsc { "name" : "Fortune", "namespace" : "com.example", "type" : "record", "fields" : [ { "name" : "message", "type" : "string" } ] }

This schema defines a Fortune record. It has a single message field of type string. Rest.li schemas are based on avro schemas, and supports maps, lists, optional fields, unions and other useful data modeling constructs.

Page 8: Introduction to rest.li

Step 1. Write a Data SchemaFortune.pdsc { "name" : "Fortune", "namespace" : "com.example", "type" : "record", "fields" : [ { "name" : "message", "type" : "string" } ] }

Rest.li schemas are designed for JSON. Here’s what a Fortune looks like in JSON: { “message”: “Today is your lucky day!” }

Page 9: Introduction to rest.li

Step 1. Write a Data SchemaFortune.pdsc { "name" : "Fortune", "namespace" : "com.example", "type" : "record", "fields" : [ { "name" : "message", "type" : "string" } ] }

Rest.li’s automatically generates a binding class from our data schema:

Page 10: Introduction to rest.li

Step 1. Write a Data SchemaFortune.pdsc { "name" : "Fortune", "namespace" : "com.example", "type" : "record", "fields" : [ { "name" : "message", "type" : "string" } ] }

Fortune.java public class Fortune extends RecordTemplate { String getMessage(); void setMessage(String); }

Rest.li’s automatically generates a binding class from our data schema:

Generated

Code

Page 11: Introduction to rest.li

Step 1. Write a Data SchemaFortune.pdsc { "name" : "Fortune", "namespace" : "com.example", "type" : "record", "fields" : [ { "name" : "message", "type" : "string" } ] }

Fortune.java public class Fortune extends RecordTemplate { String getMessage(); void setMessage(String); }

Rest.li’s automatically generates a binding class from our data schema:

Generated

CodeThis class is important, we’ll use it in a minute.

Page 12: Introduction to rest.li

Step 2. Write a REST Resource

Page 13: Introduction to rest.li

FortunesResource.java @RestLiCollection(name = "fortunes") class FortunesResource implements KeyValueResource<Long, Fortune> { ! @RestMethod.GET public Fortune get(Long key) { return new Fortune() .setMessage("Today’s your lucky day!"); } }

Step 2. Write a REST Resource

Page 14: Introduction to rest.li

FortunesResource.java @RestLiCollection(name = "fortunes") class FortunesResource implements KeyValueResource<Long, Fortune> { ! @RestMethod.GET public Fortune get(Long key) { return new Fortune() .setMessage("Today’s your lucky day!"); } }

Step 2. Write a REST ResourceRest.li annotation declares this class as our implementation of the /fortunes resource.

Page 15: Introduction to rest.li

FortunesResource.java @RestLiCollection(name = "fortunes") class FortunesResource implements KeyValueResource<Long, Fortune> { ! @RestMethod.GET public Fortune get(Long key) { return new Fortune() .setMessage("Today’s your lucky day!"); } }

Step 2. Write a REST Resource

Our collection contains Fortune entities, keyed by Long.

Page 16: Introduction to rest.li

FortunesResource.java @RestLiCollection(name = "fortunes") class FortunesResource implements KeyValueResource<Long, Fortune> { ! @RestMethod.GET public Fortune get(Long key) { return new Fortune() .setMessage("Today’s your lucky day!"); } }

Step 2. Write a REST Resource

Our collection contains Fortune entities, keyed by Long.Notice how we use the generated Fortune class here so everything is strongly typed.

Page 17: Introduction to rest.li

FortunesResource.java @RestLiCollection(name = "fortunes") class FortunesResource implements KeyValueResource<Long, Fortune> { ! @RestMethod.GET public Fortune get(Long key) { return new Fortune() .setMessage("Today’s your lucky day!"); } }

Step 2. Write a REST Resource

Here we implement HTTP GET

Page 18: Introduction to rest.li

FortunesResource.java @RestLiCollection(name = "fortunes") class FortunesResource implements KeyValueResource<Long, Fortune> { ! @RestMethod.GET public Fortune get(Long key) { return new Fortune() .setMessage("Today’s your lucky day!"); } }

Step 2. Write a REST Resource

From our resource implementation, Rest.li’s automatically generates an interface definition,

Page 19: Introduction to rest.li

FortunesResource.java @RestLiCollection(name = "fortunes") class FortunesResource implements KeyValueResource<Long, Fortune> { ! @RestMethod.GET public Fortune get(Long key) { return new Fortune() .setMessage("Today’s your lucky day!"); } }

Step 2. Write a REST Resource

fortunes.restspec.json { “path”: “/fortunes”, “supports”: [ “get” ], … }

From our resource implementation, Rest.li’s automatically generates an interface definition,

Generated

Code

Page 20: Introduction to rest.li

FortunesResource.java @RestLiCollection(name = "fortunes") class FortunesResource implements KeyValueResource<Long, Fortune> { ! @RestMethod.GET public Fortune get(Long key) { return new Fortune() .setMessage("Today’s your lucky day!"); } }

Step 2. Write a REST Resource

fortunes.restspec.json { “path”: “/fortunes”, “supports”: [ “get” ], … }

From our resource implementation, Rest.li’s automatically generates an interface definition, and client bindings.

Generated

Code

Page 21: Introduction to rest.li

FortunesResource.java @RestLiCollection(name = "fortunes") class FortunesResource implements KeyValueResource<Long, Fortune> { ! @RestMethod.GET public Fortune get(Long key) { return new Fortune() .setMessage("Today’s your lucky day!"); } }

Step 2. Write a REST Resource

FortunesBuilders.java public class FortunesBuilders { GetRequestBuilder get(); }

fortunes.restspec.json { “path”: “/fortunes”, “supports”: [ “get” ], … }

From our resource implementation, Rest.li’s automatically generates an interface definition, and client bindings.

Generated

Code Generated

Code

Page 22: Introduction to rest.li

FortunesResource.java @RestLiCollection(name = "fortunes") class FortunesResource implements KeyValueResource<Long, Fortune> { ! @RestMethod.GET public Fortune get(Long key) { return new Fortune() .setMessage("Today’s your lucky day!"); } }

Step 2. Write a REST Resource

FortunesBuilders.java public class FortunesBuilders { GetRequestBuilder get(); }

fortunes.restspec.json { “path”: “/fortunes”, “supports”: [ “get” ], … }

From our resource implementation, Rest.li’s automatically generates an interface definition, and client bindings.

Generated

Code Generated

Code

We’ll use these to build our client.

Page 23: Introduction to rest.li

Step 3. Write a Client

Page 24: Introduction to rest.li

Step 3. Write a ClientExampleClient.java Response response = restClient.sendRequest(new FortunesBuilders.get().id(1L)).get(); Fortune fortune = response.getEntity(); System.out.println(fortune.getMessage());

Page 25: Introduction to rest.li

Step 3. Write a ClientExampleClient.java Response response = restClient.sendRequest(new FortunesBuilders.get().id(1L)).get(); Fortune fortune = response.getEntity(); System.out.println(fortune.getMessage());

We use client bindings generated from our /fortune resource to build an HTTP GET Request.

Page 26: Introduction to rest.li

Step 3. Write a ClientExampleClient.java Response response = restClient.sendRequest(new FortunesBuilders.get().id(1L)).get(); Fortune fortune = response.getEntity(); System.out.println(fortune.getMessage());

We use client bindings generated from our /fortune resource to build an HTTP GET Request.

And we use the Rest.li RestClient to send the request.

Page 27: Introduction to rest.li

Step 3. Write a ClientExampleClient.java Response response = restClient.sendRequest(new FortunesBuilders.get().id(1L)).get(); Fortune fortune = response.getEntity(); System.out.println(fortune.getMessage());

We use client bindings generated from our /fortune resource to build an HTTP GET Request.

And we use the Rest.li RestClient to send the request.

Notice how everything is strongly typed.

Page 28: Introduction to rest.li

Step 3. Write a ClientExampleClient.java Response response = restClient.sendRequest(new FortunesBuilders.get().id(1L)).get(); Fortune fortune = response.getEntity(); System.out.println(fortune.getMessage());

We use client bindings generated from our /fortune resource to build an HTTP GET Request.

And we use the Rest.li RestClient to send the request.

Notice how everything is strongly typed.

But wait, this request is blocking! The .get() forces the thread block and wait for a response from the server. We’ll show how to make this non-blocking shortly.

Page 29: Introduction to rest.li

Step 4. Run it!

Page 30: Introduction to rest.li

Step 4. Run it!

“Today’s your lucky day!”

Page 31: Introduction to rest.li

Step 4. Run it!

“Today’s your lucky day!”

Great! but all our code is synchronous and blocking. What about non-blocking?

Page 32: Introduction to rest.li

Importance of async, non-blocking request handling

Page 33: Introduction to rest.li

Importance of async, non-blocking request handlingAsync processing is ideal for making multiple requests to backend systems in parallel and then composing the results of those parallel requests into a single response. For modern internet architectures, where many backend systems are involved in handling each request from a consumer, making calls in parallel to these backend systems dramatically reduces the overall time to response back to the consumer.

Page 34: Introduction to rest.li

Importance of async, non-blocking request handlingAsync processing is ideal for making multiple requests to backend systems in parallel and then composing the results of those parallel requests into a single response. For modern internet architectures, where many backend systems are involved in handling each request from a consumer, making calls in parallel to these backend systems dramatically reduces the overall time to response back to the consumer.

Servers running async code scale better because they can handle very large numbers of concurrent requests. This is because, when you write async code, no threads are blocked waiting for something to complete. If you don’t write async code and you need to handle large numbers of concurrent requests, you’ll need one thread per concurrent request. Each thread takes up considerable memory, and when you run out of memory (or max out a thread pool), the server is unable to take on more requests, resulting in timed out requests and in cases of many complex architectures, cascading failure.

Page 35: Introduction to rest.li

Async in Rest.li

Page 36: Introduction to rest.li

Async in Rest.liRest.li integrates with ParSeq for both client and server side async.

Page 37: Introduction to rest.li

Async in Rest.liRest.li integrates with ParSeq for both client and server side async.

Using ParSeq, we will write Tasks. Tasks can be composed together in parallel (par) or sequence (seq) into larger tasks. Tasks are executed asynchronously by the ParSeq engine.

Page 38: Introduction to rest.li

Async in Rest.liRest.li integrates with ParSeq for both client and server side async.

Using ParSeq, we will write Tasks. Tasks can be composed together in parallel (par) or sequence (seq) into larger tasks. Tasks are executed asynchronously by the ParSeq engine.

Let’s modify our Resource Implementation and Client to use ParSeq.

Page 39: Introduction to rest.li

Async Resource Implementation

Page 40: Introduction to rest.li

Async Resource ImplementationFortunesResource.java @RestLiCollection(name = "fortunes") class FortunesResource implements KeyValueResource<Integer, Fortune> { ! @RestMethod.GET public Task<Fortune> get(Long key) { Task<String> retrieveFortuneStr = … ; ! Task<Fortune> buildFortune = Tasks.action("getFortune", new Callable<Fortune>() { @Override public Fortune call() throws Exception { return new Fortune() .setMessage(retieveFortuneStr.get()); } }); return Tasks.seq(retrieveFortuneStr, buildFortune); } }

Page 41: Introduction to rest.li

Async Resource ImplementationFortunesResource.java @RestLiCollection(name = "fortunes") class FortunesResource implements KeyValueResource<Integer, Fortune> { ! @RestMethod.GET public Task<Fortune> get(Long key) { Task<String> retrieveFortuneStr = … ; ! Task<Fortune> buildFortune = Tasks.action("getFortune", new Callable<Fortune>() { @Override public Fortune call() throws Exception { return new Fortune() .setMessage(retieveFortuneStr.get()); } }); return Tasks.seq(retrieveFortuneStr, buildFortune); } }

Rest.li resources methods can return a ParSeq Task. Rest.li will execute them asynchronously and respond with the result when it’s available.

Page 42: Introduction to rest.li

Async Resource ImplementationFortunesResource.java @RestLiCollection(name = "fortunes") class FortunesResource implements KeyValueResource<Integer, Fortune> { ! @RestMethod.GET public Task<Fortune> get(Long key) { Task<String> retrieveFortuneStr = … ; ! Task<Fortune> buildFortune = Tasks.action("getFortune", new Callable<Fortune>() { @Override public Fortune call() throws Exception { return new Fortune() .setMessage(retieveFortuneStr.get()); } }); return Tasks.seq(retrieveFortuneStr, buildFortune); } }

Here we compose together two tasks together in sequence using Tasks.seq

Page 43: Introduction to rest.li

Async Resource ImplementationFortunesResource.java @RestLiCollection(name = "fortunes") class FortunesResource implements KeyValueResource<Integer, Fortune> { ! @RestMethod.GET public Task<Fortune> get(Long key) { Task<String> retrieveFortuneStr = … ; ! Task<Fortune> buildFortune = Tasks.action("getFortune", new Callable<Fortune>() { @Override public Fortune call() throws Exception { return new Fortune() .setMessage(retieveFortuneStr.get()); } }); return Tasks.seq(retrieveFortuneStr, buildFortune); } }

Here we compose together two tasks together in sequence using Tasks.seq

The retrieveFortuneStr task is a non-blocking IO task to get a fortune string.

Page 44: Introduction to rest.li

Async Resource ImplementationFortunesResource.java @RestLiCollection(name = "fortunes") class FortunesResource implements KeyValueResource<Integer, Fortune> { ! @RestMethod.GET public Task<Fortune> get(Long key) { Task<String> retrieveFortuneStr = … ; ! Task<Fortune> buildFortune = Tasks.action("getFortune", new Callable<Fortune>() { @Override public Fortune call() throws Exception { return new Fortune() .setMessage(retieveFortuneStr.get()); } }); return Tasks.seq(retrieveFortuneStr, buildFortune); } }

Here we compose together two tasks together in sequence using Tasks.seq

The retrieveFortuneStr task is a non-blocking IO task to get a fortune string.

The buildFortune task will create a Fortune from the result of the retrieveFortuneStr task. Notice how .get() is used to access the value of the completed retrieveFortuneStr.

Page 45: Introduction to rest.li

Async ClientFortuneClient.java Task<Response<Fortune>> getFortune = parSeqClient.createTask(new FortunesBuilders.get().id(1L)); !final Task<Void> printFortune = Tasks.action("printFortune", new Runnable() { @Override public void run() { Response<Fortune> response = getFortune.get(); Fortune fortune = getResponseEntity(); System.out.println(fortune.getMessage()); } }); !engine.run(Tasks.seq(getFortune, printFortune)); !printFortune.await();

Page 46: Introduction to rest.li

Async ClientFortuneClient.java Task<Response<Fortune>> getFortune = parSeqClient.createTask(new FortunesBuilders.get().id(1L)); !final Task<Void> printFortune = Tasks.action("printFortune", new Runnable() { @Override public void run() { Response<Fortune> response = getFortune.get(); Fortune fortune = getResponseEntity(); System.out.println(fortune.getMessage()); } }); !engine.run(Tasks.seq(getFortune, printFortune)); !printFortune.await();

We’ll use ParSeqRestClient, which can create Tasks from Rest.li requests.

Page 47: Introduction to rest.li

Async ClientFortuneClient.java Task<Response<Fortune>> getFortune = parSeqClient.createTask(new FortunesBuilders.get().id(1L)); !final Task<Void> printFortune = Tasks.action("printFortune", new Runnable() { @Override public void run() { Response<Fortune> response = getFortune.get(); Fortune fortune = getResponseEntity(); System.out.println(fortune.getMessage()); } }); !engine.run(Tasks.seq(getFortune, printFortune)); !printFortune.await();

Here will compose together two tasks, also in sequence using Tasks.seq.

Page 48: Introduction to rest.li

Async ClientFortuneClient.java Task<Response<Fortune>> getFortune = parSeqClient.createTask(new FortunesBuilders.get().id(1L)); !final Task<Void> printFortune = Tasks.action("printFortune", new Runnable() { @Override public void run() { Response<Fortune> response = getFortune.get(); Fortune fortune = getResponseEntity(); System.out.println(fortune.getMessage()); } }); !engine.run(Tasks.seq(getFortune, printFortune)); !printFortune.await();

Here will compose together two tasks, also in sequence using Tasks.seq.The getFortune task will make a non-blocking request to our /fortunes resource.

Page 49: Introduction to rest.li

Async ClientFortuneClient.java Task<Response<Fortune>> getFortune = parSeqClient.createTask(new FortunesBuilders.get().id(1L)); !final Task<Void> printFortune = Tasks.action("printFortune", new Runnable() { @Override public void run() { Response<Fortune> response = getFortune.get(); Fortune fortune = getResponseEntity(); System.out.println(fortune.getMessage()); } }); !engine.run(Tasks.seq(getFortune, printFortune)); !printFortune.await();

Here will compose together two tasks, also in sequence using Tasks.seq.The getFortune task will make a non-blocking request to our /fortunes resource.

The printFortune task will print the result of the getFortune task to stdout.

Page 50: Introduction to rest.li

Async ClientFortuneClient.java Task<Response<Fortune>> getFortune = parSeqClient.createTask(new FortunesBuilders.get().id(1L)); !final Task<Void> printFortune = Tasks.action("printFortune", new Runnable() { @Override public void run() { Response<Fortune> response = getFortune.get(); Fortune fortune = getResponseEntity(); System.out.println(fortune.getMessage()); } }); !engine.run(Tasks.seq(getFortune, printFortune)); !printFortune.await(); We’ll use a ParSeq engine

directly here to execute the async flow.

Page 51: Introduction to rest.li

Async ClientFortuneClient.java Task<Response<Fortune>> getFortune = parSeqClient.createTask(new FortunesBuilders.get().id(1L)); !final Task<Void> printFortune = Tasks.action("printFortune", new Runnable() { @Override public void run() { Response<Fortune> response = getFortune.get(); Fortune fortune = getResponseEntity(); System.out.println(fortune.getMessage()); } }); !engine.run(Tasks.seq(getFortune, printFortune)); !printFortune.await();

If our client is a command line application, we need to wait for our async tasks to complete before exiting.

Page 52: Introduction to rest.li

Run it again!

Page 53: Introduction to rest.li

Run it again!

“Today’s your lucky day!”

Page 54: Introduction to rest.li

Run it again!

“Today’s your lucky day!”

Much better. Now we can scale.

Page 55: Introduction to rest.li

Next Steps

To learn more, try the Rest.li Quickstart Tutorial

!!

For more details on ParSeq, see the ParSeq Wiki