-
Notifications
You must be signed in to change notification settings - Fork 546
Rest.li 2.0 Proposed Changes
Rest.li is a Java framework that allows you to easily create clients and servers that use a REST style of communication. Rest.li is based on an inversion-of-control model, in which the framework handles most of the data flow and client/server interaction transparently, and calls code you supply at the appropriate time.
This document describes how to use Rest.li to build RESTful clients and servers. The first section introduces key architectural elements and provides an overview of the development process. The remainder of the document serves as a detailed reference to Rest.li features. It is not necessary to read this entire document before using Rest.li. Once you understand the basic principles, you can refer to specific sections of this guide when you have questions. If you just want to get started exploring a simple sample implementation, go to "Quickstart Guide - a step-by-step tutorial on the basics":./Quickstart:-A-Tutorial-Introduction-to-Rest.li.
h2. Chapters
h2. Chapter 1. Introduction to Rest.li Client/Server Architecture
Rest.li allows you to build and access RESTful servers and clients, without worrying too much about the details of HTTP or JSON. You simply define a data model (using an schema definition language) and resources (Java classes that that supply or act on the appropriate data in response to HTTP requests), and Rest.li takes care of everything else. In this section, we'll describe the flow of control and data between a Rest.li server and client, and also look briefly at the development process, so you understand what tasks you need to do to develop Rest.li clients and servers, and what Rest.li does for you automatically.
The Rest.li server framework consists of libraries that provide annotations and helper classes for describing your resources, as well as an inversion-of-control dispatcher that handles incoming requests and automatically invokes the appropriate methods in your resources.
The following diagram provides a high-level view of the interaction and data flow between a Rest.li client and server. The yellow arrows indicate the flow of requests out of the client and into the server, while dark blue arrows represent the server's response. You as a developer implement the Resource classes in the server. Rest.li provides the the platform code and infrastructure for dispatching and handling requests, and also generates the Record Templates and RequestBuilder classes
Data and Control Flow Between a Rest.li Server and Clienth3. Asynchronous APIs
Rest.li is built on simple asynchronous APIs. These APIs allow both servers to run in non-blocking event based frameworks and allow client code to be written to make non-blocking calls. This has a couple major benefits, on the server it means that our servers can be leaner and scale to high request throughput because we don't need large, per request, thread pools. On the client, it makes it easy to stitch together multiple requests to servers in sophisticated flows where independent calls can be made in parallel.
Rest.li's client implementation is Netty-based and is designed to work seamlessly with "ParSeq":https://github.com/linkedin/parseq to construct complex asynchronous request flows.
There are several server implementations:
- "Servlet":https://github.com/linkedin/rest.li/wiki/Rest.li-with-Servlet-Containers, Battle tested and ready for production use. Containers supporting "Servlet 3.0 API":http://download.oracle.com/otndocs/jcp/servlet-3.0-fr-eval-oth-JSpec/ are required to benefit from asynchronous, non-blocking request processing. Jetty 8.x supports Servlet 3.0 and has been used in large production environments.
- "Netty":https://github.com/linkedin/rest.li/wiki/Rest.li-with-Netty, Experimental
- Embedded Jetty, primarily for integration testing as it's trivial to spin up as part of a test suite
See "Asynchronous Resources":https://github.com/linkedin/rest.li/wiki/Rest.li-User-Guide#asynchronous-resources for more details on how to handle requests using non-blocking request processing.
The remainder of this guide will assume use of the servlet server implementation.
h3. Server Data Flow
Starting with the server (on the right in the diagram above), the following steps occur when a request is submitted to a Rest.li server:
The R2 transport layer receives a request (HTTP + JSON), and sends it on to Rest.li (R2 is a separate library that provides HTTP transport services - it is independent of Rest.li, but is included with the Rest.li code base, and is designed to work well with Rest.li)
Rest.li's routing logic inspects the request's URI path and determines which target resource (a Java class) the server has defined to handle that request
Rest.li invokes the appropriate methods of the resource object, passing in any necessary Java parameters
Rest.li serializes the response object and passes it back to the requesting client via the R2 transport layer
We'll look at what you, as a developer, need to do to support this data flow shortly, but you probably noticed that Rest.li does most of the work. The primary task of the developer is to define the data model to be supported by your server, and implement the resource classes that can produce that data. Rest.li handles the details of routing requests, instantiating resource classes and invoking methods on objects at the right time.
When writing resource classes, it is important to understand that Rest.li constructs a new instance of the appropriate resource class to handle each request. This means that resource objects cannot store state across multiple requests. Any long-lived resources should be managed separately (see "Dependency Injection":/linkedin/rest.li/wiki/Rest.li-User-Guide/#wiki-DependencyInjection below).
h3. Client Data Flow
Rest.li also provides support for writing clients. Clients issue requests by instantiating a RequestBuilder object that supports methods that allow details of the request to be specified. The RequestBuilder object generates a Request object that can be passed to Rest.li and sent to the server via the R2 transport layer. When the server responds (as detailed above), the client receives the request via the R2 transport, and Rest.li produces a RecordTemplate object (matching the object instantiated by the server) and provides the object to the client.
Both client and server work with the same java representations of the server's data model. Note that you do not need to use a Rest.li based client to communicate with a Rest.li server. However, Rest.li supports type-safe data exchange via Java interfaces when using Rest.li for both client and server.
h3. Development Flow
Next, let's briefly look at the basic development flow required to implement a client and server to support the data flow described in the previous section. Your tasks as a developer are basically to define your data model using a simple modeling language and to implement Java classes that act on or produce that data. Rest.li supports these tasks with a combination of base classes and code generation.
The following diagram illustrates the major steps in building servers and clients based on the Rest.li framework. The numbers in the diagram correspond to the sequence in which tasks are done. Blue boxes represent classes you will write, while green boxes represent components that are created by Rest.li's code generators. Black arrows indicate a code generation process; red dashed lines indicate the use of classes that allow server and clients to exchange data.
Rest.li Development FlowLet's look at each step.
- Step 1. The first step in building a Rest.li application is to define your data schema using "Pegasus Data Schemas":/linkedin/rest.li/wiki/DATA-Data-Schema-and-Templates. The Pegasus Data Schema format uses an simple Avro-like syntax.
- In Step 2, a Rest.li code generator creates Java classes that represent the data model defined in Step 1. These RecordTemplate classes serve as the Java representation of the data in both the server and client.
- Step 3 is to implement the server Resource classes and define the operations they support. Rest.li provides a set of annotations and base classes that allow you to map Resource classes to REST endpoints and to specify methods of your Resource classes to respond REST operations, such as GET or PUT. Your Resource classes are expected to return data using instances of the RecordTemplate classes generated in Step 2.
- In Step 4, Rest.li generates an interface description (IDL) file that provides a simple, textual, machine-readable specification of the server resources implemented in Step 3. The IDL is considered the source of truth for the interface contract between the server and its clients. The IDL itself is a language-agnostic JSON format. Rest.li uses this IDL along with the original data schema files to suport automatically generating human-readable documentation, which can be requested from a server. See "IDL Compatibility":/linkedin/rest.li/wiki/Gradle-build-integration#compatibility for build details and how run the IDL check in "backwards" and "ignore" modes.
- Step 5 is to create your server application, which involves leveraging a few Rest.li classes to instantiate the Rest.li server, set up the transport layer, and and supply Rest.li with the location (class path) of your Resource classes.
- In Step 6, Rest.li generates classes known as RequestBuilders that correspond to the server resource classes. These RequestBuilders are used by clients to create requests to the server. Together with the RecordTemplate and Resource classes, RequestBuilders provide convenient and type-safe mechanisms for working with the data models supported by the server.
- Finally, Step 7 is to implement one or more clients. Clients issue requests by instantiating the RequestBuilder classes generated in Step 6. These RequestBuilders produce Requests that are passed to Rest.li to issue requests to a server.
The "Quickstart Guide - a step-by-step tutorial on the basics":/linkedin/rest.li/wiki/Quickstart:-A-Tutorial-Introduction-to-Rest.li provides a step-by-step walk through this development process, and demonstrates the nuts and bolts, including build scripts and other infrastructure required to execute these steps.
h2. Chapter 2. Rest.li Server
In this chapter, we will look at Rest.li support for implementing servers.
h3. Runtimes
Rest.li supports the following runtimes:
"Servlet containers":https://github.com/linkedin/rest.li/wiki/Rest.li-with-Servlet-Containers (e.g. Jetty)
h3. R2 Filter Configuration
Rest.li servers can be configured with different R2 filters as per your use case. How the filters are configured depends on which dependency injection framework (if any) you are using. For example, please take a look at the compression wiki page to see how we can configure a server for compression. Another example: to add a SimpleLoggingFilter
with Spring, you would have to do the following (full file here):
<!-- Example of how to add filters, here we'll enable logging and snappy compression support -->
<bean id="loggingFilter" class="com.linkedin.r2.filter.logging.SimpleLoggingFilter" />
Other R2 filters can also be configured in a similar way.
h3. Defining Data Models
The first step in building a Rest.li application is to define your data schema using "Pegasus Data Schemas":/linkedin/rest.li/wiki/DATA-Data-Schema-and-Templates. The Pegasus Data Schema format uses an simple Avro-like syntax to define your data model in a language-independent way. Rest.li provides code generators to create Java classes that implement your data model. See "Pegasus Data Schemas":/linkedin/rest.li/wiki/DATA-Data-Schema-and-Templates for full details.
h3. Writing Resources
Once you have defined your data models, the principle programming task when implementing a Rest.li server is to create resource classes. In Rest.li, resource classes define the RESTful endpoints your server provides. You create a resource class by adding a class level annotation and by implementing or extending a Rest.li interface or base class corresponding to the annotation. The annotations help describe the mapping from your Java code to the REST interface protocol. When possible, the framework uses conventions to help minimize the annotations you need to write.
Steps to define a resource class:
- The class must have the default constructor. The default constructor will be used by Rest.li to instantiate the resource class for each request execution.
- The class must be annotated with one of the Resource Annotations
- If required by the annotation, the class must @implement@ the necessary Resource interface or extend one of the convenience base classes that implements the interface
- To expose methods on the resource, each method must either: ** Override a standard method from the Resource interface ** Include the necessary method-level annotation as described in the Resource Methods section below
- For each exposed method, each parameter must either: ** Be part of the standard signature, for overridden methods ** Be annotated with one of the parameter-level annotations described for the Resource Method.
- All documentation is written in the resource source file using javadoc (or scaladoc, see below for details)
Here is a simple example of a Resource class extends a convenience base class, uses an annotation to defines a REST end-point ("fortunes"), and provides a GET endpoint by overriding the standard signature of the @get()@ method of the base class.
/**
* A collection of fortunes, keyed by random number.
*/
@RestLiCollection(name = "fortunes", namespace = "com.example.fortune")
public class FortunesResource extends CollectionResourceTemplate<Long, Fortune>
{
/**
* Gets a fortune for a random number.
*/
@Override
public Fortune get(Long key)
{
// retrieve data and return a Fortune object ...
}
}
This interface implements an HTTP GET:
> GET /fortunes/1
...
< { "fortune": "Your lucky color is purple" }
Note that Rest.li does not automatically use the names of your Java identifiers - class names, method names, and parameter names have no direct bearing on the interface your resource exposes through annotations.
The above example supports the GET operation by overriding the CollectionResourceTemplate
, and you can also choose to support other operations by overriding other methods. However, you can also define any method of your class as handling operations by using Resource Annotations, described in detail in the next section.
h4. Documenting Resources
Rest.li resources are documented in the resource source files using javadoc. When writing resources, developers simply add any documentation as javadoc to their java resource classes, methods and method params. It is recommended that developers follow the "javadoc style guidelines":http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html for all formatting so that their documentation is displayed correctly.
Rest.li will automatically extract this javadoc and include it in all generated "interface definitions" (.restspec.json files), and generated client bindings. This allows REST API clients and tools to easily gain access to the documentation. For example, "Rest.li API Hub":https://github.com/linkedin/rest.li-api-hub is an opensource web UI that displays REST API documentation, including all javadoc, for Rest.li APIs.
Scaladoc is also supported, see "Scala Integration":https://github.com/linkedin/rest.li/wiki/Scala-Integration for details.
h4. Resource Annotations
Resource annotations are used to mark and register a class as providing as Rest.li resource. One of a number of annotations may be used, depending on the "Interface Pattern":https://github.com/linkedin/rest.li/wiki/Modeling-Resources-with-Rest.li the resource is intended to implement. Briefly, the options are:
|. Resource type |. Annotation |_. Interface or Base class | | Collection | @RestLiCollection | For simple keys, implement @CollectionResource@ or extend @CollectionResourceTemplate@. For complex key implement @ComplexKeyResource@, extend @ComplexKeyResourceTemplate@, or implement @KeyValueResource@ for use cases requiring extensive customization | | Simple | @RestLiSimpleResource | Implement @SimpleResource@, extend @SimpleResourceTemplate@ or implement @SingleObjectResource@ for use cases requiring extensive customization | | Association | @RestLiAssociation | Implement @AssociationResource@, extend @AssociationResourceTemplate@, or implement @KeyValueResource@ for use cases requiring extensive customization | | Actions | @RestLiActions | N/A |
h5. @RestLiCollection
The @@RestLiCollection@ annotation is applied to classes to mark them as providing a Rest.li collection resource. Collection resources model a collection of entities, where each entity is referenced by a key. See the description of the "Collection Resource Pattern":https://github.com/linkedin/rest.li/wiki/Modeling-Resources-with-Rest.li#wiki-Collection for more details.
The supported annotation parameters are:
- @name@ - required, defines the name of the resource.
- @namespace@ - optional, defines the namespace for the resource. Default is empty (root namespace). The namespace of the resource appears in the IDL, and is used as the package name for the generated client builders.
- @keyName@ - optional, defines the key name for the resource. Default is "<ResourceName>Id".
- @parent@ - optional, defines the parent resource for this resource. Default is root.
Classes annotated with @@RestLiCollection@ must implement the @CollectionResource@ interface. The @CollectionResource@ interface requires two generic type parameters:
- @K@, which is the key type for the resource.
- @V@, which is the value type for the resource, a.k.a., the entity type.
The key type for a collection resource must be one of:
- @String@
- @Boolean@
- @Integer@
- @Long@
- A Pegasus Enum (any enum defined in a @.pdsc@ schema)
- Custom Type (see below for details)
- Complex Key (A pegasus record, any subclass of @RecordTemplate@ generated from a @.pdsc@ schema)
The value type for a collection resource must be a pegasus record, any subclass of @RecordTemplate@ generated from a @.pdsc@ schema.
For convenience, Collection resources may extend @CollectionResourceTemplate@ rather than directly implementing the @CollectionResource@ interface.
For example:
@RestLiCollection(name = "fortunes", namespace = "com.example.fortune", keyName = "fortuneId")
public class FortunesResource extends CollectionResourceTemplate<Long, Fortune>
{
...
}
h5. Sub-Resources
Sub-resources may be defined by setting the "parent" field on @RestLiCollection
to the class of the parent resource of the sub-resource.
For example, a subresource of the fortunes resource would have a URI path of the form:
/fortunes/{fortuneId}/subresource
Parent resource keys can be accessed by sub-resources. For example:
@RestLiCollection(name = "subresource", namespace = "com.example.fortune", parent = FortunesResource.class)
public class SubResource extends CollectionResourceTemplate<Long, SubResourceEntity>
{
@RestMethod.Get
public Greeting get(Long key, @Keys PathKeys keys) {
Long parentId = keys.getAsLong("fortuneId");
...
}
...
}
Alternatively, if not using free form methods, the path key can retrieved from the resource context. This approach may be deprecated in future versions in favor of @Keys
.
public SubResourceEntity get(Long subresourceKey)
{
Long parentId = getContext().getPathKeys().getAsLong("fortuneId");
...
}
For details on how to make requests to sub-resources from a client, see: Calling Sub-resources
h5. @RestLiCollection with Complex Key
Classes implmementing @ComplexKeyResource@ can use a record type as key. This allows for arbitrary complex hierarchical structures to be used to key a collection resource, unlike CollectionResources, which only support primitive type keys (or typerefs to primitive types). @ComplexKeyResourceTemplate@ is a convenient base class to extend when implementing a @ComplexKeyResource@.
The full interface is:
public interface ComplexKeyResource<K extends RecordTemplate, P extends RecordTemplate, V extends RecordTemplate> ...
A complex key consists of a @Key@ and @Parameter@ part. The @Key@ should uniquely identify the entities of the collection while the parameters may optionally be added to allow additional information that is not used to lookup an entity, such as a version tag for concurrency control.
Since the parameters are often not needed and @EmptyRecord@ may be used in the generic signature of a ComplexKeyResource
to indicate that no "Parameters" are used to key the collection.
Example:
@RestLiCollection(name = "widgets", namespace = "com.example.widgets")
public class WidgetResource implements extends ComplexKeyResourceTemplate<WidgetKey, EmptyRecord, Widget>
{
public Widget get(ComplexResourceKey<WidgetKey, EmptyRecord> ck)
{
WidgetKey key = ck.getKey();
int number = key.getNumber();
String make = key.getThing().getMake();
String model = key.getThing().getModel();
return lookupWidget(number, make, model);
}
}
To use EmptyRecord
, restli-common
must be in the dataModel
dependencies for the api project where client bindings are generated, e.g.:
api/build.gradle:
dependencies {
...
dataModel spec.product.pegasus.restliCommon
}
Where WidgetKey.pdsc
is defined by the schema:
{
"type": "record",
"name": "WidgetKey",
"namespace": "com.example.widget",
"fields": [
{"name": "number", "type": "string"},
{
"name": "thing", "type": {
"type": "record",
"name": "Thing",
"fields": [
{"name": "make", "type": "string"},
{"name": "model", "type": "string"}
]
}
}
]
}
Example request:
curl "http:///widgets/number=1&thing.make=adruino&thing.model=uno
If params are added, they are represented in the url under the "$params" prefix, like so:
curl "http:///widgets/number=1&thing.make=adruino&thing.model=uno&$params.version=1
The implementation of complex key collection is identical to the regular @RestLiCollection@ with the exception that it extends @ComplexKeyResourceTemplate@ (or directly implements @ComplexKeyResource@), and takes three type parameters instead of two - key type, key parameter type and value type, each extending @RecordTemplate.
For details on how a complex key is represented in a request URL see "Rest.li Protocol: Complex Types":/linkedin/rest.li/wiki/Rest.li-Protocol#complex-types
h5. @RestLiSimpleResource
The @@RestLiSimpleResource@ annotation is applied to classes to mark them as providing a Rest.li simple resource. Simple resources model an entity which is a singleton in a particular scope. See the description of the "Simple Resource Pattern":https://github.com/linkedin/rest.li/wiki/Modeling-Resources-with-Rest.li#wiki-Simple for more details.
The supported annotation parameters are:
- @name@ - required, defines the name of the resource.
- @namespace@ - optional, defines the namespace for the resource. Default is empty (root namespace). The namespace of the resource appears in the IDL, and is used as the package name for the generated client builders.
- @parent@ - optional, defines the parent resource for this resource. Default is root.
Classes annotated with @@RestLiSimpleResource@ must implement the @SimpleResource@ interface. The @SimpleResource@ interface requires a generic type parameter @V@, which is the value type for the resource, a.k.a., the entity type. The value type for a simple resource must be a pegasus record, any subclass of @RecordTemplate@ generated from a @.pdsc@ schema.
For convenience, Simple resources may extend @SimpleResourceTemplate@ rather than directly implementing the @SimpleResource@ interface.
Examples:
@RestLiSimpleResource(name = "todaysPromotedProduct", namespace = "com.example.product")
public class TodaysPromotedProductResource extends SimpleResourceTemplate<Product>
{
...
}
h5. @RestLiAssociation
The @@RestLiAssociation@ annotation is applied to classes to mark them as providing a Rest.li association resource. Association resources model a collection of relationships between entities. Each relationship is referenced by the keys of the entities it relates, and may define attributes on the relation itself. See the description of the "Association Resource Pattern":https://github.com/linkedin/rest.li/wiki/Modeling-Resources-with-Rest.li#wiki-Association for more details.
For Example:
@RestLiAssociation(name = "memberships", namespace = "com.example",
assocKeys = {
@Key(name = "memberId", type = Long.class),
@Key(name = "groupId", type = Long.class)
}
)
public class MembershipsAssociation extends AssociationResourceTemplate<Membership>
{
@Override
public Membership get(CompoundKey key)
{
return lookup(key.getPartAsLong("memberId", key.getPartAsLong("groupId"));
}
}
curl http:///memberships/memberId=1&groupId=10
The supported annotation parameters are:
- @name@ - required, defines the name of the resource.
- @namespace@ - optional, defines the namespace for the resource. Default is empty (root namespace). The namespace of the resource appears in the IDL, and is used as the package name for the generated client builders.
- @parent@ - optional, defines the parent resource for this resource. Default is root.
- @assocKeys@ - required, defines the list of keys for the association resource. Each key must declare its name and type.
Classes annotated with @@RestLiAssociation@ must implement the @AssociationResource@ interface. The @AssociationResource@ interface requires a single generic type parameter:
- @V@, which is the value type for the resource, a.k.a., the entity type.
The value type for an association resource must be a subclass of @RecordTemplate@ generated from a @.pdsc@ schema.
Note that for association resources, they key type is always @CompoundKey@, with key parts as defined in the @assocKeys@ parameter of the class's annotation.
For convenience, Association resources may extend @AssociationResourceTemplate@ rather than directly implementing the @AssociationResource@ interface.
h5. @RestLiActions
The @@RestLiActions@ annotation is applied to classes to mark them as providing a Rest.li action set resource. Action set resources do not model any resource pattern - they simply group together a set of custom actions.
For example:
@RestLiActions(name = "simpleActions",
namespace = "com.example")
public class SimpleActionsResource {
@Action(name="echo")
public String echo(@ActionParam("input") String input)
{
return input;
}
}
The supported annotation parameters are:
- @name@ - required, defines the name of the resource.
- @namespace@ - optional, defines the namespace for the resource. Default is empty (root namespace).
Action set resources do not have a key or value type, and do not need to @implement@ any framework interfaces.
h4. Resource Methods
Resource methods are operations a resource can perform. Rest.li defines a standard set of resource methods, each with its own interface pattern and intended semantics.
The set of possible resource methods is constrained by the resource type, as described in the table below:
|. Resource Type |. Collection |. Simple |. Association |_. Action Set | | GET | x | x | x | | | BATCH_GET / GET_ALL | x | | x | | | FINDER | x | | x | | | CREATE / BATCH_CREATE | x | | | | | UPDATE / PARTIAL_UPDATE | x | x | x | | | BATCH_UPDATE / BATCH_PARTIAL_UPDATE | x | | x | | | DELETE | x | x | x | | | BATCH_DELETE | x | | x | | | ACTION | x | x | x | x |
In the section below, @K@ is used to denote the resource's key type, and @V@ is used to denote the resource's value type. Remember that for association resources, @K@ is always @CompoundKey@.
h5. GET
The GET resource method is intended to retrieve a single entity representation based upon its key or without a key from a simple resource. GET should not have any visible side effects, i.e., it should be safe to call whenever the client wishes.
Resources providing the GET resource method must override one of the following method signatures:
for collection and association resources:
public V get(K key);
for simple resources:
public V get();
Get methods can also be annotated if not overriding a base class method. GET supports a method signature with a wrapper return type:
for collection and association resources:
@RestMethod.Get
public GetResult<V> getWithStatus(K key);
for simple resources:
@RestMethod.Get
public GetResult<V> getWithStatus();
An annotated get method may also have arbitrary query params added:
@RestMethod.Get
public GetResult<V> get(K key, @QueryParam("viewerId") String viewerId);
The return type @GetResult@ allows users to set an arbitrary HTTP status code for the response. For more information about the @RestMethod.Get@ annotation, see "Free-form Resources":#FreeFormResources.
h5. BATCH_GET
The BATCH_GET resource method is intended to retrieve multiple entity representations given their keys. BATCH_GET should not have any visible side effects, i.e., it should be safe to call whenever the client wishes. However, this is not something enforced by the framework and it is up to the application developer that there are no side-effects.
Resources providing the BATCH_GET resource method must override the following method signature:
public Map<K, V> batchGet(Set<K> ids);
@@RestMethod.BatchGet@ may be used to indicate a batch get method instead of overriding the batchGet method of a base class.
Resources may also return @BatchResult@, which allows errors to be returned along with entities that were successfully retrieved.
Example of a batch get:
public BatchResult<Long, Greeting> batchGet(Set<Long> ids)
{
Map<Long, Greeting> batch = new HashMap<Long, Greeting>();
Map<Long, RestLiServiceException> errors = new HashMap<Long, RestLiServiceException>();
for (long id : ids)
{
Greeting g = _db.get(id);
if (g != null)
{
batch.put(id, g);
}
else
{
errors.put(id, new RestLiServiceException(HttpStatus.S_404_NOT_FOUND));
}
}
return new BatchResult<Long, Greeting>(batch, errors);
}
Clients should make requests to a batch resource using @buildKV()@ (not @build()@, it is deprecated), e.g.:
new FortunesBuilders().batchGet().ids(...).buildKV()
h5. GET_ALL
When a GET is requested on a collection or association resource with no key provided (e.g. /myResource), the GET_ALL resource method is invoked, if present. The GET_ALL resource method retrieves all entities for the collection and supports the same pagination facilities as a finder.
public List<V> getAll(@Context PagingContext pagingContext);
@@RestMethod.GetAll@ may be used to indicate a get all method instead of overriding the getAll method of a base class.
To directly control the total and metadata returned by a get all method, do not override getAll, instead create a new method with the @@RestMethod.GetAll@ annotation and return a @CollectionResult@ rather than a list, e.g.:
@RestMethod.GetAll
public CollectionResult<Widgets, WidgetsMetadata> getAllWidgets(@Context PagingContext pagingContext)
{
// ...
return new CollectionResult<Widgets, WidgetsMetadata>(pageOfWidgets, total, metadata);
}
When returning a CollectionResult from GetAll the behavior is identical to a finder, see below finder documentation for additional details on use of CollectionResult.
h5. FINDER
FINDER methods are intended to model query operations, i.e., they retrieve an ordered list of 0 or more entities based on criteria specified in the query parameters. Finder results will automatically be paginated by the rest.li framework. Like GET methods, FINDER methods should not have side effects.
Resources may provide zero or more FINDER resource methods. Each finder method must be annotated with the @@Finder@ annotation.
Pagination default to start=0 and count=10. Clients may set both of these parameters to any desired value.
The @@Finder@ annotation takes a single required parameter, which indicates the name of the finder method.
For example:
/*
You can access this FINDER method via /resources/order?q=findOrder&buyerType=1&buyerId=309&orderId=1208210101
*/
@RestLiCollection(name="order",keyName="orderId")
public class OrderResource extends CollectionResourceTemplate<Integer,Order>
{
@Finder("findOrder")
public List<Order> findOrder(@Context PagingContext
@QueryParam("buyerId") Integer
@QueryParam("buyerType") Integer
@QueryParam("orderId") Integer orderId)
throws InternalException
{
...
}
...
Finder methods must return either:
- @List@
- @CollectionResult<V, MetaData>@
- @BasicCollectionResult@, a subclass of @CollectionResult@
- a subclass of one the above
Every parameter of a finder method must be annotated with one of:
- @@Context@ - indicates that the parameter provides framework context to the method. Currently all @@Context@ parameters must be of type @PagingContext@.
- @@QueryParam@ - indicates that the value of the parameter is obtained from a request query parameter. The value of the annotation indicates the name of the query parameter. Duplicate names are not allowed for the same finder method.
- @@ActionParam@ - similar to Query Param, but the parameter information will be located in the request body. Generally, @@QueryParam@ is preferred over @@ActionParam@.
- @@AssocKey@ - indicates that the value of the parameter is a partial association key, obtained from the request. The value of the annotation indicates the name of the association key, which must match the name of an @@Key@ provided in the @assocKeys@ field of the @@RestLiAssociation@ annotation.
Parameters marked with @@QueryParam@, @@ActionParam@, and @@AssocKey@ may also be annotated with @@Optional@, which indicates that the parameter is not required. The @@Optional@ annotation may specify a String value, indicating the default value to be used if the parameter is not provided in the request. If the method parameter is of primitive type, a default value must be specified in the @@Optional@ annotation.
Valid types for query parameters are:
- @String@
- @boolean@ / @Boolean@
- @int@ / @Integer@
- @long@ / @Long@
- @float@ / @Float@
- @double@ / @Double@
- @Enum@
- Custom types (see the bottom of this section)
- Record template types (any subclass of @RecordTemplate@ generated from a @.pdsc@ schema)
- Arrays of one of the types above, e.g. @String[]@, @long[]@, ...
@Finder("simpleFinder")
public List<V> simpleFind(@Context PagingContext context);
@Finder("complexFinder")
public CollectionResult<V, MyMetaData> complexFinder(@Context(defaultStart = 10, defaultCount = 100)
PagingContext context,
@AssocKey("key1") Long key,
@QueryParam("param1") String requiredParam,
@QueryParam("param2") @Optional String optionalParam);
h6. Typerefs (Custom Types)
Custom types can be any java type so long as it has a coercer and a typeref schema, even java classes from libraries such as Date. To create a query parameter that uses a custom type, you will need to write a coercer and a typeref schema for the type you want to use. See the "typeref documentation":https://github.com/linkedin/rest.li/wiki/DATA-Data-Schema-and-Templates for details.
First, for the coercer you will need to write an implementation of DirectCoercer that converts between your custom type and some simpler underlying type, like String or Double. By convention, the coercer should be an internal class of the custom type it coerces. Additionally, the custom type should register its own coercer in a static code block.
If this is not possible (for example, if you want to use a java built-in class like Date or URI as a custom type) then you can write a separate coercer class and register the coercer with the private variable declaration
private static final Object REGISTER_COERCER = Custom.registerCoercer(new ObjectCoercer(), CustomObject.class);
Typeref Schema
The purpose of the typeref schemas is to keep track of the underlying type of the custom Type, and the location of the custom type's class, and, if necessary, the location of its coercer. The basic appearance of the typeref schema is shown below:
{
"type" : "typeref",
"name" : "CustomObjectRef",
"namespace" : "com.linkedin.example" // namespace of the typeref
"ref" : "string", // underlying type that the coercer converts to/from
"java" : {
"class" : "com.linkedin.example.CustomObject", // location of the custom type class
"coercerClass" : "com.linkedin.example.CustomObjectCoercer" // only needed if the custom
// type itself cannot contain
// the coercer as an internal class.
}
}
This typeref can then be referenced in other schemas:
{
"type": "record",
"name": "ExampleRecord",
...
"fields": [
{"name": "member", "type": "com.linkedin.example.CustomObjectRef"}
...
]
}
And the generated java data templates will automatically coerce from CustomObjectRef to CustomObject when accessing the member field:
CustomObject o = exampleRecord.getMember();
Once java data templates are generated, the typeref may also be used in Keys, query parameters or action parameters:
Keys:
@RestLiCollection(name="entities",
namespace = "com.example",
keyTyperefClass = CustomObjectRef.class)
public class EntitiesResource extends CollectionResourceTemplate<Urn, CustomObject>
Compound keys:
@RestLiAssociation(name="entities", namespace="com.example",
assocKeys={@Key(name="o", type=CustomObject.class, typeref=CustomObjectRef.class)})
Query parameters:
@QueryParam(value="o", typeref=CustomObjectRef.class) CustomObject o
@QueryParam(value="oArray", typeref=CustomObjectRef.class) CustomObject[] oArray
h5. CREATE
CREATE methods are intended to model creation of new entities from their representation. In CREATE, the resource implementation is responsible for assigning a new key to the created entity. CREATE methods are neither safe nor idempotent.
Resources providing the CREATE resource method must override the following method signature:
public CreateResponse create(V entity);
The returned @CreateResponse@ object indicates the HTTP status code to be returned (defaults to 201 CREATED), as well as an optional id for the newly created entity. If provided, the id will be written into the "X-LinkedIn-Id" header by calling @toString()@ on the id object.
@@RestMethod.Create@ may be used to indicate a create method instead of overriding the create method of a base class.
h5. BATCH_CREATE
BATCH_CREATE methods are intended to model creation of a group of new entities from their representations. In BATCH_CREATE, the resource implementation is responsible for assigning a new key to each created entity. BATCH_CREATE methods are neither safe nor idempotent.
Resources providing the BATCH_CREATE resource method must override the following method signature:
public BatchCreateResult<K, V> batchCreate(BatchCreateRequest<K, V> entities);
The @BatchCreateRequest@ object wraps a list of entity representations of type @V@.
The returned @BatchCreateResult@ object wraps a list of @CreateResponse@ objects (see CREATE). The @CreateResponse@ objects are expected to be returned in the same order and position as the respective input objects.
@BatchCreateRequest@ and @BatchCreateResult@ support the generic type parameter @K@ to allow for future extension.
@@RestMethod.BatchCreate@ may be used to indicate a batch create method instead of overriding the batchCreate method of a base class.
Example of a batch create:
public BatchCreateResult<Long, Greeting> batchCreate(BatchCreateRequest<Long, Greeting> entities)
{
List<CreateResponse> responses = new ArrayList<CreateResponse>(entities.getInput().size());
for (Greeting g : entities.getInput())
{
responses.add(create(g));
}
return new BatchCreateResult<Long, Greeting>(responses);
}
public CreateResponse create(Greeting entity)
{
entity.setId(_idSeq.incrementAndGet());
_db.put(entity.getId(), entity);
return new CreateResponse(entity.getId());
}
Error details can be returned in any CreateResponse by providing a RestLiServiceException, for example:
public BatchCreateResult<Long, Greeting> batchCreate(BatchCreateRequest<Long, Greeting> entities) {
List<CreateResponse> responses = new ArrayList<CreateResponse>(entities.getInput().size());
...
if (...) {
RestLiServiceException exception = new RestLiServiceException(HttpStatus.S_406_NOT_ACCEPTABLE, "...");
exception.setServiceErrorCode(...);
exception.setErrorDetails(...);
responses.add(new CreateResponse(exception));
}
...
return new BatchCreateResult<Long, Greeting>(responses);
}
h5. UPDATE
UPDATE methods are intended to model updating an entity with a given key by setting its value (overwriting the entire entity). UPDATE has side effects, but is idempotent, i.e., repeating the same update operation has the same effect as calling it once.
Resources may choose whether to allow an UPDATE of an entity that does not already exist, in which case it should be created. This is different from CREATE because the client specifies the key for the entity to be created. Simple resources use UPDATE as a way to create the singleton entity.
Resources providing the UPDATE resource method must override one of the following method signatures:
for collection and association resources:
public UpdateResponse update(K key, V entity);
for simple resources:
public UpdateResponse update(V entity);
The returned @UpdateResponse@ object indicates the HTTP status code to be returned.
@@RestMethod.Update@ may be used to indicate a update method instead of overriding the update method of a base class.
h5. BATCH_UPDATE
BATCH_UPDATE methods are intended to model updating a set of entities with specified keys by setting their values (overwriting each entity entirely). BATCH_UPDATE has side effects, but is idempotent, i.e., repeating the same batch update operation has the same effect as calling it once.
Resources may choose whether to allow BATCH_UPDATE for entities that do not already exist, in which case each entity should be created. This is different from BATCH_CREATE because the client specifies the keys for the entities to be created.
Resources providing the BATCH_UPDATE resource method must override the following method signature:
public BatchUpdateResult<K, V> batchUpdate(BatchUpdateRequest<K, V> entities);
@BatchUpdateRequest@ contains a map of entity key to entity value.
The returned @BatchUpdateResult@ object indicates the @UpdateResponse@ for each key in the @BatchUpdateRequest@. In the case of failures, @RestLiServiceException@ objects may be added to the @BatchUpdateResult@ for the failed keys.
@@RestMethod.BatchUpdate@ may be used to indicate a batch update method instead of overriding the batchUpdate method of a base class.
Example of a batch update:
public BatchUpdateResult<Long, Greeting> batchUpdate(BatchUpdateRequest<Long, Greeting> entities)
{
Map<Long, UpdateResponse> responseMap = new HashMap<Long, UpdateResponse>();
for (Map.Entry<Long, Greeting> entry : entities.getData().entrySet())
{
responseMap.put(entry.getKey(), update(entry.getKey(), entry.getValue()));
}
return new BatchUpdateResult<Long, Greeting>(responseMap);
}
public UpdateResponse update(Long key, Greeting entity)
{
Greeting g = _db.get(key);
if (g == null)
{
return new UpdateResponse(HttpStatus.S_404_NOT_FOUND);
}
_db.put(key, entity);
return new UpdateResponse(HttpStatus.S_204_NO_CONTENT);
}
h5. PARTIAL_UPDATE
PARTIAL_UPDATE methods are intended to model updating part of the entity with a given key. PARTIAL_UPDATE has side effects, and in general is not guaranteed to be idempotent.
Resources providing the PARTIAL_UPDATE resource method must override the following method signature:
public UpdateResponse update(K key, PatchRequest<V> patch);
The returned @UpdateResponse@ object indicates the HTTP status code to be returned.
Rest.li provides tools to make it easy to handle partial updates to your resources. A typical update function should look something like this:
@Override
public UpdateResponse update(String key, PatchRequest<YourResource> patch )
{
YourResource resource = _db.get(key); // Retrieve the resource object from somewhere
if (resource == null)
{
return new UpdateResponse(HttpStatus.S_404_NOT_FOUND);
}
try
{
PatchApplier.applyPatch(resource, patch); // Apply the patch.
// Be sure to save the resource if necessary
}
catch (DataProcessingException e)
{
return new UpdateResponse(HttpStatus.S_400_BAD_REQUEST);
}
return new UpdateResponse(HttpStatus.S_204_NO_CONTENT);
}
The PatchApplier automatically updates resources defined using the Pegasus Data format. The Rest.li client classes provide support for constructing patch requests, but here is an example update request using curl:
curl -X POST localhost:/fortunes/1 -d '{"patch": {"$set": {"fortune": "you will strike it rich!"}}}'
@@RestMethod.PartialUpdate@ may be used to indicate a partial update method instead of overriding the partialUpdate method of a base class.
h5. Inspecting partial updates to selectively update fields in a backing store
It is possible to inspect the partial update and selectively write only the changed fields to a store.
For example, to update only the street field of this address entity:
{
"address": {
"street": "10th",
"city": "Sunnyvale"
}
}
The partial update to change just the street field is:
{
"patch": {
"address": {
"$set": {
"street": "9th"
}
}
}
}
For the service code to selectively update just the street field (e.g. UPDATE addresses SET street=:street WHERE key=:key). The partial update can be inspected and the selective update if only the street field is changed:
@Override
public UpdateResponse update(String key, PatchRequest<YourResource> patchRequest)
{
try
{
DataMap patch = patchRequest.getPatchDocument();
boolean selectivePartialUpdateApplied = false;
if(patch.containsKey("address") && patch.size() == 1)
{
DataMap address = patch.getDataMap("address");
if(address.containsKey("$set") && address.size() == 1)
{
DataMap set = address.getDataMap("$set");
if(address.containsKey("street") && address.size() == 1)
{
String street = address.getString("street");
selectivePartialUpdateApplied = true;
// update only the street, since its the only thing this patch requests to change
}
}
}
if(selectivePartialUpdateApplied == false)
{
// no selective update available, update the whole record with PatchApplier and return the result
}
}
catch (DataProcessingException e)
{
return new UpdateResponse(HttpStatus.S_400_BAD_REQUEST);
}
return new UpdateResponse(HttpStatus.S_204_NO_CONTENT);
}
h5. Creating partial updates
To create a request to modify field(s), PatchGenerator can be used, .e.g:
Fortune fortune = new Fortune().setMessage("Today's your lucky day.");
PatchRequest<Fortune> patch = PatchGenerator.diffEmpty(fortune);
Request<Fortune> request = new FortunesBuilders().partialUpdate().id(1L).input(patch).build();
PatchGenerator.diff(original, revised)
can also be used to create a minimal partial update.
h5. BATCH_PARTIAL_UPDATE
BATCH_PARTIAL_UPDATE methods are intended to model partial updates of multiple entities given their keys. BATCH_PARTIAL_UPDATE has side effects, and in general is not guaranteed to be idempotent.
Resources providing the BATCH_PARTIAL_UPDATE resource method must override the following method signature:
public BatchUpdateResult<K, V> batchUpdate(BatchPatchRequest<K, V> patches);
The @BatchPatchRequest@ input contains a map of entity key to @PatchRequest@.
The returned @BatchUpdateResult@ object indicates the @UpdateResponse@ for each key in the @BatchPatchRequest@. In the case of failures, @RestLiServiceException@ objects may be added to the @BatchUpdateResult@ for the failed keys.
@@RestMethod.BatchPartialUpdate@ may be used to indicate a batch partial update method instead of overriding the batchPartialUpdate method of a base class.
Example of a batch partial update:
public BatchUpdateResult<Long, Greeting> batchUpdate(BatchPatchRequest<Long, Greeting> entityUpdates)
{
Map<Long, UpdateResponse> responseMap = new HashMap<Long, UpdateResponse>();
for (Map.Entry<Long, PatchRequest<Greeting>> entry : entityUpdates.getData().entrySet())
{
responseMap.put(entry.getKey(), update(entry.getKey(), entry.getValue()));
}
return new BatchUpdateResult<Long, Greeting>(responseMap);
}
public UpdateResponse update(Long key, PatchRequest<Greeting> patch)
{
Greeting g = _db.get(key);
if (g == null)
{
return new UpdateResponse(HttpStatus.S_404_NOT_FOUND);
}
try
{
PatchApplier.applyPatch(g, patch);
}
catch (DataProcessingException e)
{
return new UpdateResponse(HttpStatus.S_400_BAD_REQUEST);
}
_db.put(key, g);
return new UpdateResponse(HttpStatus.S_204_NO_CONTENT);
}
h5. DELETE
DELETE methods are intended to model deleting (removing) an entity with a given key on collection and association resources or without a key on simple resources. DELETE has side effects, but is idempotent.
Resources providing the DELETE resource method must override one of the following method signatures:
for collection and association resources:
public UpdateResponse delete(K key);
for simple resources:
public UpdateResponse delete();
The returned @UpdateResponse@ object indicates the HTTP status code to be returned.
@@RestMethod.Delete@ may be used to indicate a delete method instead of overriding the delete method of a base class.
h5. BATCH_DELETE
BATCH_DELETE methods are intended to model deleting (removing) multiple entities given their keys. BATCH_DELETE has side effects, but is idempotent.
Resources providing the BATCH_DELETE resource method must override the following method signature:
public BatchUpdateResult<K, V> batchDelete(BatchDeleteRequest<K, V> ids);
The @BatchDeleteRequest@ input contains the list of keys to be deleted. @BatchDeleteRequest@ accepts a generic type parameter @V@ for future extension.
The returned @BatchUpdateResult@ object indicates the @UpdateResponse@ for each key in the @BatchDeleteRequest@. In the case of failures, @RestLiServiceException@ objects may be added to the @BatchUpdateResult@ for the failed keys.
@@RestMethod.BatchDelete@ may be used to indicate a batch delete method instead of overriding the batchDelete method of a base class.
Example of a batch delete:
public BatchUpdateResult<Long, Greeting> batchDelete(BatchDeleteRequest<Long, Greeting> deleteRequest)
{
Map<Long, UpdateResponse> responseMap = new HashMap<Long, UpdateResponse>();
for (Long id : deleteRequest.getKeys())
{
responseMap.put(id, delete(id));
}
return new BatchUpdateResult<Long, Greeting>(responseMap);
}
public UpdateResponse delete(Long key)
{
boolean removed = _db.remove(key) != null;
return new UpdateResponse(removed ? HttpStatus.S_204_NO_CONTENT : HttpStatus.S_404_NOT_FOUND);
}
h5. ACTION
ACTION methods are very flexible, and do not specify any standard behavior.
Resources may provide zero or more ACTION resource methods. Each action must be annotated with the @@Action@ annotation.
The @@Action@ annotation supports the following parameters:
- @name@ Required, the name of the action resource method.
- @resourceLevel@ Optional, defaults to @ResourceLevel.ANY@, which indicates that the action is defined directly on the containing resource and does not support an entity key as a URI parameter. @ResourceLevel.COLLECTION@ indicates that the action is defined on the containing association or collection resource and does not support an entity key as a URI parameter. @ResourceLevel.ENTITY@ indicates that the action is defined on the entity and it requires an entity key as a URI parameter when the containing resource is an association or collection resource. If the containing resource is a simple resource @ResourceLevel.ENTITY@ indicates that the action is defined directly on the resource and does not support an entity key as a URI parameter.
- @returnTyperef@ Optional, defaults to no typeref. Indicates a Typeref to be used in the IDL for the action's return parameter. Useful for actions that return primitive types.
Each parameter to an action method must be annotated with @@ActionParam@, which takes the following annotation parameters:
- @value@ Required, string name for the action parameter. If this is the only annotation, parameter, it may be specified without being explicitly named, e.g., @@ActionParam("paramName")@
- @typeref@ Optional, Typeref to be used in the IDL for the parameter.
Parameters of action methods may also be annotated with @@Optional@, which indicates that the parameter is not required in the request. The @@Optional@ annotation may specify a String value, which specifies the default value to be used if the parameter is not provided in the request. If the method parameter is of primitive type, a default value must be specified in the @@Optional@ annotation.
Valid parameter types and return types for action are:
- @String@
- @boolean@ / @Boolean@
- @int@ / @Integer@
- @long@ / @Long@
- @float@ / @Float@
- @double@ / @Double@
- @ByteString@
- @Enum@
- @RecordTemplate@ or a subclass of @RecordTemplate@
- @FixedTemplate@ or a subclass of @FixedTemplate@
- @AbstractArrayTemplate@ or a subclass of @AbstractArrayTemplate@, e.g., @StringArray@, @LongArray@, etc.
- @AbstractMapTemplate@ or a subclass of @AbstractMapTemplate@, e.g., @StringMap@, @LongMap@, etc.
Similar to @GetResult@, since 1.5.8, Rest.li supports an @ActionResult@ wrapper return type that allows you to specify an arbitrary HTTP status code for the response.
Simple example:
@Action(name="action")
public void doAction();
A more complex example, illustrating multiple parameters:
@Action(name="sendTestAnnouncement",
resourceLevel= ResourceLevel.ENTITY)
public void sendTestAnnouncement(@ActionParam("subject") String subject,
@ActionParam("message") String message,
@ActionParam("emailAddress") String emailAddress)
h5. @ActionParam vs. @QueryParam
@@ActionParam@ and @@QueryParam@ are used in different methods. @@ActionParam@ is only allowed in Action methods, while @@QueryParam@ is allowed in all non-Action methods. Besides, they are also different in terms of how the parameter data is sent to the server. If a parameter is annotated with @@QueryParam@, the information will be sent in the request url. If a parameter is annotated with @@ActionParam@, the information will be sent in the request body. Therefore, one advantage of using @@ActionParam@ would be that the sent parameter can be encoded. One disadvantage is that the purpose of the request itself can become less clear if one only examines the url.
h5. Returning Nulls
Resource methods should never explicitly return @null@. If the rest.li framework detects this, it will return an HTTP @500@ back to the client with a message indicating ‘Unexpected null encountered’. The only exceptions to this rule are ACTION and GET. If an ACTION resource method returns @null@, the rest.li framework will return an HTTP @200@. If a GET returns @null@, the rest.li framework will return an HTTP @404@.
Also note that the HTTP @500@ will also be generated by the rest.li framework if subsequent data structures inside of resource method responses are null or contain null. This applies to any data structure that is not a RecordTemplate. For example, all of the the following would cause an HTTP @500@ to be returned. Note this list is not exhaustive:
- A @BatchCreateResult@ returning a @null@ results list.
- A @BatchCreateResult@ returning a valid list that as a @null@ element inside of it.
- A @CreateResponse@ returning a @null@ for the @HttpStatus@.
- A @BatchUpdateResult@ returning a @null@ key in the results map.
- A @BatchUpdateResult@ returning a @null@ errors map.
- A @BatchUpdateResult@ returning a valid errors map, but with a @null@ key or @null@ value inside of it.
It is good practice to make sure that @null@ is never returned in any part of resource method responses, with the exception of RecordTemplate classes, ACTION methods and GET methods.
h4. ResourceContext
@ResourceContext@ provides access to the context of the current request. @ResourceContext@ is injected into resources that implement the @BaseResource@ interface, by calling @setContext()@.
For resources extending @CollectionResourceTemplate@, @AssociationResourceTemplate@, or @ResourceContextHolder@, the current context is available by calling @getContext()@.
@ResourceContext@ provides methods to access the raw request, as well as parsed values from the request. @ResourceContext@ also provides some control over the generated response, such as the ability to set response headers.
h4. Resource Templates
h5. CollectionResourceTemplate
@CollectionResourceTemplate@ provides a convenient base class for collection resources. Subclasses may selectively override relevant methods. If a method is not overridden, the framework will recognize that your resource does not support this method, and will return a 404 if clients attempt to invoke it. Note that unsupported methods will be omitted from your resources IDL (see "Restspec IDL":https://github.com/linkedin/rest.li/wiki/Rest.li-User-Guide#wiki-RestspecIDL for details).
@CollectionResourceTemplate@ defines methods for all of the CRUD operations. Subclasses may also implement FINDER and ACTION methods, by annotating as described above.
public CreateResponse create(V entity);
public BatchCreateResult<K, V> batchCreate(BatchCreateRequest<K, V> entities);
public V get(K key);
public Map<K, V> batchGet(Set<K> ids);
public UpdateResponse update(K key, V entity);
public BatchUpdateResult<K, V> batchUpdate(BatchUpdateRequest<K, V> entities);
public UpdateResponse update(K key, PatchRequest<V> patch);
public BatchUpdateResult<K, V> batchUpdate(BatchPatchRequest<K, V> patches);
public UpdateResponse delete(K key);
public BatchUpdateResult<K, V> batchDelete(BatchDeleteRequest<K, V> ids);
h5. SimpleResourceTemplate
@SimpleResourceTemplate@ provides a convenient base class for simple resources. Subclasses may selectively override relevant methods. If a method is not overridden, the framework will recognize that your resource does not support this method, and will return a 404 if clients attempt to invoke it. Note that unsupported methods will be omitted from your resources IDL (see "Restspec IDL":https://github.com/linkedin/rest.li/wiki/Rest.li-User-Guide#wiki-RestspecIDL for details).
@SimpleResourceTemplate@ defines methods for GET, UPDATE and DELETE methods. Subclasses may also implement ACTION methods, by annotating as described above.
public V get();
public UpdateResponse update(V entity);
public UpdateResponse delete();
h5. AssociationResourceTemplate
@AssociationResourceTemplate@ provides a convenient base class for association resources. Subclasses may selectively override relevant methods. If a method is not overridden, the framework will recognize that your resource does not support this method, and will return a 404 if clients attempt to invoke it. Note that unsupported methods will be omitted from your resources IDL (see description of "Restspec IDL":https://github.com/linkedin/rest.li/wiki/Rest.li-User-Guide#wiki-RestspecIDL for details).
@AssociationResourceTemplate@ defines methods for all of the CRUD operations except CREATE. Association resources should implement CREATE by providing up-sert semantics on UPDATE. Subclasses may also implement FINDER and ACTION methods, by annotating as described above.
public CreateResponse create(V entity);
public BatchCreateResult<CompoundKey, V> batchCreate(BatchCreateRequest<CompoundKey, V> entities);
public V get(CompoundKey key);
public Map<CompoundKey, V> batchGet(Set<CompoundKey> ids);
public UpdateResponse update(CompoundKey key, V entity);
public BatchUpdateResult<CompoundKey, V> batchUpdate(BatchUpdateRequest<CompoundKey, V> entities);
public UpdateResponse update(CompoundKey key, PatchRequest<V> patch);
public BatchUpdateResult<CompoundKey, V> batchUpdate(BatchPatchRequest<CompoundKey, V> patches);
public UpdateResponse delete(CompoundKey key);
public BatchUpdateResult<CompoundKey, V> batchDelete(BatchDeleteRequest<CompoundKey, V> ids);
h4. Free-form Resources
Resource Templates provide a convenient way to implement the recommended signatures for the basic CRUD operations (CREATE, GET, UPDATE, PARTIAL_UPDATE, DELETE, and respective batch operations). When possible, we recommend using the resource templates to ensure that your interface remains simple and uniform.
However, it is sometimes necessary to add custom parameters to CRUD operations. In these cases, the fixed signatures of resource templates are too constraining. The solution is to create a free-form resource by implementing the corresponding marker interface for your resource and annotating CRUD methods with @@RestMethod.*@ annotations.The @KeyValueResource@ interface is the marker interface for collection and association resources where the @SingleObjectResource@ interface is the marker interface for simple resources.
public class FreeFormCollectionResource implements KeyValueResource<K, V>
{
@RestMethod.Create
public CreateResponse myCreate(V entity);
@RestMethod.BatchCreate
public BatchCreateResult<K, V> myBatchCreate(BatchCreateRequest<K, V> entities);
@RestMethod.Get
public V myGet(K key);
@RestMethod.GetAll
public CollectionResult<V, M> myGetAll(@Context PagingContext pagingContex);
@RestMethod.BatchGet
public Map<K, V> myBatchGet(Set<K> ids);
@RestMethod.Update
public UpdateResponse myUpdate(K key, V entity);
@RestMethod.BatchUpdate
public BatchUpdateResult<K, V> myBatchUpdate(BatchUpdateRequest<K, V> entities);
@RestMethod.PartialUpdate
public UpdateResponse myUpdate(K key, PatchRequest<V> patch);
@RestMethod.BatchPartialUpdate
public BatchUpdateResult<K, V> myBatchUpdate(BatchPatchRequest<K, V> patches);
@RestMethod.Delete
public UpdateResponse myDelete(K key);
@RestMethod.BatchDelete
public BatchUpdateResult<K, V> myBatchDelete(BatchDeleteRequest<K, V> ids);
}
public class FreeFormSimpleResource implements SingleObjectResource<V>
{
@RestMethod.Get
public V myGet();
@RestMethod.Update
public UpdateResponse myUpdate(V entity);
@RestMethod.Delete
public UpdateResponse myDelete();
}
The advantage of explicitly annotating each resource method is that you can add custom query parameters (see description of @@QueryParam@ for FINDER resource method) and take advantage of wrapper return types. Custom query parameters must be defined after the fixed parameters shown above.
@RestMethod.Get
public V myGet(K key, @QueryParam("myParam") String myParam);
@RestMethod.Get
public GetResult<V> getWithStatus(K key);
Note that each resource may only provide one implementation of each CRUD method, e.g., it is invalid to annotate two different methods with @@RestMethod.Get@.
h4. Things to Remember about Free-form Resources
- Free-form resources allow you to add query parameters to CRUD methods
- Resource Templates should be used when possible
- Free-form resources must implement one of the @KeyValueResource@ and @SingleObjectResource@ marker interfaces
- Methods in free-form resources must be annotated with appropriate @@RestMethod.*@ annotations.
- Methods in free-form resources must use the same return type and initial signature as the corresponding Resource Template method
- Methods in free-form resources may add additional parameters after the fixed parameters
- Free-form resources may not define multiple implementations of the same resource method.
h4. Returning Errors
There are several mechanisms available for resources to report errors to be returned to the caller. Regardless of which mechanism is used, resources should be aware of the resulting HTTP status code, and ensure that meaningful status codes are used. Remember that @4xx@ codes should be used to report client errors (errors that the client may be able to resolve), and @5xx@ codes should be used to report server errors.
h5. Return @null@ for GET
If a resource method returns @null@ for GET, the framework will automatically generate a @404@ response to be sent to the client.
Note that returning @null@ for resource methods is generally forbidden with the exception of GET and ACTION. Returning a @null@ for a GET returns a 404 and returning a @null@ for an ACTION returns 200.
Returning a @null@ for any other type of resource method will cause the rest.li framework to return an HTTP @500@ to be sent back to the client with a message indicating 'Unexpected null encountered'. This is described in detail above: Returning Nulls
h5. Return any HTTP Status Code in a CreateResponse/UpdateResponse
@CreateResponse@ and @UpdateResponse@ allow an "Http Status Code":http://en.wikipedia.org/wiki/List_of_HTTP_status_codes to be provided. Status codes in the @4xx@ and @5xx@ ranges may be used to report errors.
h5. Throw RestLiServiceException to return a 4xx/5xx HTTP Status Code
The framework defines a special exception class, @RestLiServiceException@, which contains an "Http Status Code":http://en.wikipedia.org/wiki/List_of_HTTP_status_codes field, as well as other fields that are returned to the client in the body of the HTTP response. Resources may throw @RestLiServiceException@ or a subclass to prompt the framework to return an HTTP error response.
h5. Throw another exception
All exceptions originating in application code are caught by the framework and used to generate an HTTP response. If the exception does not extend @RestLiServiceException@, an HTTP @500@ response will be sent.
h5. Return errors as part of a BatchResult
BATCH_GET methods may return errors for individual items as part of a @BatchResult@ object. Each error is represented as a @RestLiServiceException@ object. In this case, the overall status will still be an HTTP @200@.
public BatchResult<K, V> batchGet((Set<K> ids)
{
Map<K, V> results = ...
Map<K, RestLiServiceException> errors = ...
...
return new BatchResult(results, errors);
}
h4. Handling Errors on the Client
When making requests using @RestClient@. A @ResponseFuture@ is always returned, e.g.:
ResponseFuture<Greeting> future = restClient.sendRequest(new GreetingsBuilders.get().id(1L));
This future might contain an error response. When calling @ResponseFuture.getResponse()@ the default behavior is for a @RestLiResponseException@ to be thrown if the response contains an error response. Error responses are all 400 and 500 series HTTP status code.
For example:
try
{
Greeting greeting = restClient.sendRequest(new GreetingsBuilders.get().id(1L)).getResponseEntity();
// handle successful response
}
catch (RestLiResponseException e)
{
if(e.getStatus() == 400) {
// handle 400
} else {
// ... handle other status codes or rethrow
}
}
Alternatively, @ErrorHandlingBehavior.TREAT_SERVER_ERROR_AS_SUCCESS@ can be set when making a request. If set, @.getResponse()@ will not throw @RestLiResponseException@ even if the response contains a 400 or 500 series HTTP status code.
For example:
Response<Greeting> response = restClient.sendRequest(new GreetingsBuilders.get().id(1L),
ErrorHandlingBehavior.TREAT_SERVER_ERROR_AS_SUCCESS).getResponse();
if(response.getStatus() == 200)
{
// handle successful response
}
else if (response.getStatus() == 404)
{
// handle 404
}
else
{
// ... handle other status codes or rethrow
}
However, because error responses do not contain an entity. calling @ResponseFuture.getResponseEntity()@ or @Response.getEntity()@ will always throw a @RestLiResponseException@ for 400 or 500 series HTTP status code, regardless of @ErrorHandlingBehavior@.
h4. Configuring how errors are represented in an HTTP response
By default, rest.li returns an extensive HTTP error response that includes:
- HTTP Status Code (manditory)
- X-LinkedIn-Error-Response header (this will be renamed to X-RestLi-Error-Response shortly)
- A response body containing:
-
- A full stack trace
-
- A service error code (optional)
-
- Application specific error details (optional)
The error response format configured to return only a subset of these parts via RestLiConfig. E.g.:
restliConfig.setErrorResponseFormat(ErrorResponseFormat.MESSAGE_AND_DETAILS);
When rest.li server application code throws an exception, if the exception is of type RestLiServiceException, then the error message provided by the RestLiServiceException is used for the error message in the HTTP response. But if any other java exception is thrown, rest.li automatically provides a default error message of "Error in application code" in the error response. This default error message may be customized via RestLiConfig as well. E.g.:
restliConfig.setInternalErrorMessage("Internal error, please try again later.");
h4. Validation
h5. Basic Data Schema Validation
Data is not validated by default. If clients or servers need to validate received data, they can do it manually using "Data to Schema Validation":https://github.com/linkedin/rest.li/wiki/DATA-Data-Schema-and-Templates#data-to-schema-validation
For example, to validate the input of a create or update request, one might do:
ValidationResult validationResult =
ValidateDataAgainstSchema.validate(dataTemplate.data(), dataTemplate.schema(), new ValidationOptions());
if (!validationResult.isValid())
{
throw new RestliServiceException(HttpStatus.S_422_UNPROCESSABLE_ENTITY, validationResult.getMessages().toString());
}
HTTP 400 status codes are sometimes used in this case as well. Neither are usually considered "incorrect". 422 is a more specific status code, whereas 400 is more general. Although 422 is not an official status code according to the HTTP specification, it is well recognized and documented.
Validation may also be used to "fixup" data maps. This is particularly useful if a data map (a) contains string values that must be coerced to numeric primitives or (b) does not contain values for fields that have defaults but needs to have them populated (since, defaults are usually inferred from the schema when reading fields via data templates and are not materialized automatically). This can be done by running validation with the desired RequiredMode and CoercionMode and then using the "fixed" data map that is returned by the validator.
ValidationResult validationResult = ValidateDataAgainstSchema.validate(greeting.data(), greeting.schema(), new ValidationOptions(RequiredMode.FIXUP_ABSENT_WITH_DEFAULT, CoercionMode.STRING_TO_PRIMITIVE));
if (validationResult.isValid())
{
Greeting fixed = new Greeting((DataMap)validationResult.getFixed());
...
}
h5. Custom validation rules
Rest.li includes "some customizable validators":https://github.com/linkedin/rest.li/tree/master/data/src/main/java/com/linkedin/data/schema/validator, such as "strlen" and "regex", that can be added to schemas. Developers can write additional validators for any specific need.
For example, to use "strlen" to validate a string between 1 and 20 chars long, we add it to the "validate" map of the field in the schema. E.g.
{
"name": "Fortune",
"namespace": "com.example",
"type": "record",
"fields": [
{
"name": "message",
"type": "string",
"validate": {
"strlen": {
"min": 1,
"max": 20
}
}
}
]
}
Validator names are case sensitive and must have a matching a validator java class in the current classpath. Rest.li finds the validator class by uppercasing the first letter of the validator name and appending the "Validator" suffix. E.g. "strlen" maps to "StrlenValidator":https://github.com/linkedin/rest.li/blob/master/data/src/main/java/com/linkedin/data/schema/validator/StrlenValidator.java. Developers writing additional validators only need to write a class extending @AbstractValidator@ and include it in the classpath to use it.
To enforce validation, use @ValidateDataAgainstSchema.validate()@, passing in a @DataSchemaAnnotationValidator@. E.g.
Fortune record = new Fortune(); // the record template class to validate
DataSchemaAnnotationValidator validator = DataSchemaAnnotationValidator(record.schema());
ValidationOptions options = new ValidationOptions(RequiredMode.FIXUP_ABSENT_WITH_DEFAULT, CoercionMode.STRING_TO_PRIMITIVE);
ValidationResult result = ValidateDataAgainstSchema.validate(record.data(), record.schema(), options, validator);
if (!result.isValid()) {
throw new RestLiServiceException(HttpStatus.S_422_UNPROCESSABLE_ENTITY, result.getMessages().toString());
}
Additional details are described in the javadoc for "DataSchemaAnnotationValidator":https://github.com/linkedin/rest.li/blob/master/data/src/main/java/com/linkedin/data/schema/validator/DataSchemaAnnotationValidator.java.
h4. Field Projection
Rest.li provides built-in support for field projections, i.e., structural filtering of responses. The support includes "Java Projection Bindings":https://github.com/linkedin/rest.li/wiki/How-to-use-projections-in-Java and a "JSON Projection wire protocol":https://github.com/linkedin/rest.li/wiki/Projections. The projection is applied separately to each entity object in the response, i.e., to the value-type of the CollectionResource or AssociationResource. If the invoked method is a FINDER that returns a List, the projection is applied to each element of the list individually. Likewise, if the invoked method is a BATCH_GET that returns a Map<K, V>, the projection is applied to each value in the map individually.
For resource methods that return CollectionResult, the Rest.li framework also provides the ability to project the Metadata and as well as the Paging that is sent back to the client. More info on Collection Pagination is provided below.
The Rest.li server framework recognizes the "fields", "metadataFields" or "pagingFields" query parameters in the request. If available, the Rest.li framework then parses each of these as individual @MaskTrees@. The resulting @MaskTrees@ are available through the ResourceContext (see above) or directly to the resource methods.
Projection can also be toggled between @AUTOMATIC@ and @MANUAL@. The latter precludes the Rest.li framework from performing any projection while the former forces the Rest.li framework to perform the projection.
Additional details are described in "How to use projections in Java":https://github.com/linkedin/rest.li/wiki/How-to-use-projections-in-Java
h3. Collection Pagination
Rest.li provides helper methods to implement collection pagination, but requires each resource to implement core pagination logic itself. Rest.li pagination uses positional indices to specify page boundaries.
The Rest.li server framework automatically recognizes the @"start"@ and @"count"@ parameters for pagination, parses the values of these parameters, and makes them available through a @PagingContext@ object. FINDER methods may request the @PagingContext@ by declaring a method parameter annotated with @@Context@ (see above).
FINDER methods are expected to honor the @PagingContext@ requirements, i.e., to return only the subset of results with logical indices @>= start@ and @< start+count@.
The Rest.li server framework also includes support for returning CollectionMetadata as part of the response. CollectionMetadata includes pagination info such as:
- The requested @start@
- The requested @count@
- The @total@ number of results (before pagination)
- Links to the previous and next pages of results
FINDER methods that can provide the @total@ number of matching results should do so by returning an appropriate @CollectionResult@ or @BasicCollectionResult@ object.
The Rest.li server framework will automatically construct @Link@ objects to the previous page (if start > 0) and the next page (if the response includes count results).
Example request illustrating use of start & count pagination parameters, and resulting links in CollectionMetadata:
$ curl "http://localhost:1338/greetings?q=search&start=4&count=2"
{
"elements": [ ... ],
"paging": {
"count": 2,
"links": [
"href": "/greetings?count=10&start=10&q=search",
"rel": "next",
"type": "application/json"
],
"start": 4
}
}
h4. Dependency Injection
The Rest.li server framework controls the lifecycle of instances of Resource classes, instantiating a new Resource object for each request. It is therefore frequently necessary/desirable for resources to use a dependency-injection mechanism to obtain the objects they depend upon, e.g., database connections or other resources.
Rest.li includes direct support for the following dependency injection frameworks:
- "Spring":http://www.springsource.org/ via the "rest.li/spring bridge":https://github.com/linkedin/rest.li/wiki/Spring-Dependency-Injection
- "Guice":https://code.google.com/p/google-guice/ via the "rest.li/guice bridge":https://github.com/linkedin/rest.li/wiki/Guice-Dependency-Injection
Other dependency injection frameworks can be used as well. Rest.li provides an extensible dependency-injection mechanism, through the @ResourceFactory@ interface.
The most broadly used dependency injection mechanism is based on mapping JSR-330 annotations to the Spring ApplicationContext, and is provided by the @InjectResourceFactory@ from @restli-contrib-spring@. This is the recommended approach, described below.
Resource classes may annotate fields with @@Inject@ or @@Named@. If only @@Inject@ is specified, the field will be bound to a bean from the Spring ApplicationContext based on the type of the field. If @@Named@ is used, the field will be bound to a bean with the same name. All beans must be in the root Spring context.
h4. Asynchronous Resources
Rest.li allows resources to return results asynchronously through a "ParSeq":https://github.com/linkedin/parseq/wiki @Promise@, @Task@, or @Callback@. For example, a getter can be declared in any of the following ways:
@RestMethod.Get
public Promise<Greeting> get(Long key)
{
// return a promise (e.g. SettablePromise) and set it asynchronously
}
@RestMethod.Get
public Task<Greeting> get(Long key)
{
// set up some ParSeq tasks and return the final Task
return Tasks.seq(Tasks.par(...), ...);
}
@RestMethod.Get
public void get(Long key, @CallbackParam Callback<Greeting> callback)
{
// use the callback asynchronously
}
These method signatures can be mixed arbitrarily with the synchronous signatures, including in the same resource class. For instance, simple methods can be implemented synchronously and slow methods can be implemented asynchronously. However, multiple implementations of the same REST method with different signatures may not be provided.
You can also use the asynchronous resource templates in order to implement asynchronous Rest.li resources. The templates are -
AssociationResourceAsyncTemplate
AssociationResourcePromiseTemplate
AssociationResourceTaskTemplate
CollectionResourceAsyncTemplate
CollectionResourcePromiseTemplate
CollectionResourceTaskTemplate
ComplexKeyResourceAsyncTemplate
ComplexKeyResourcePromiseTemplate
ComplexKeyResourceTaskTemplate
SimpleResourceAsyncTemplate
SimpleResourcePromiseTemplate
SimpleResourceTaskTemplate
The Rest.li server will automatically start any @Task@ that is returned by a @Task@-based method by running it through a ParSeq engine. Also, @Promise@-based methods are guaranteed to be run through a @Task@ in the ParSeq engine, including those that do not explicitly take a ParSeq @Context@. @Callback@-based methods do not receive special treatment.
h3. On-line Documentation
Rest.li has built in an on-line documentation generator that dynamically generates resource IDL and pdsc schemas hosted in the server. The documentation is available in both HTML and JSON formats, and there are 3 approaches to access the documentation:
HTML. The relative path to HTML documentation is @restli/docs/@. For example, the documentation URI for resource @http://://@ is @GET http://://restli/docs/rest/@ (@GET@ is the "HTTP GET method":http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3, which is the default for web browser). The root URL, i.e. @http://://restli/docs@ displays list of all accessible resources and data schemas in the server. Use it as a starting point for HTML documentation. Remember to remove the @@ part if there is no context path.
Use the @format=json@ query parameter on any of the HTML pages above. For example, @GET http://://restli/docs/rest/?format=json@ for resource documentation and @GET http://://restli/docs/data/<full_name_of_data_schema>?format=json@ for schema documentation. Homepage @GET http://://restli/docs/?format=json@ is also available, which aggregates all resources and data schemas.
Use the "HTTP OPTIONS method":http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2. Simply replace the HTTP GET method with the OPTIONS method when accessing a resource without using the @format@ query parameter. This approach only works for resources, and there is no need for the special @restli/docs/@ path. For example, @OPTIONS http://://@.
The JSON format is structured as following:
{
"models": {
"": { },
"": { }
}
"resources": {
"": { },
"": { }
}
}
When accessing the JSON format of data schema, the @resources@ key exists but the value is always empty.
h4. Initialize on-line documentation generator
- @documentationRequestHandler@: instance of @RestLiDocumentationRequestHandler@ class, default to null. Specify which implementation of documentation generator is used in the server. If null, the on-line documentation feature is disabled.
- @serverNodeUri@: URI prefix of the server without trailing slash, default to empty string (""). The URI prefix is mainly used in the HTML documents by @DefaultDocumentationRequestHandler@ to properly generate links. Usually this should be an absolute path.
h2. Chapter 3. Rest.li Client Framework
The Rest.li client framework provides support for accessing resources defined using Rest.li. The client framework consists of two parts:
-
RequestBuilder
classes, which provide an interface for creating REST requests to access a specific method of a resource. Request builders work entirely in-memory, and do not communicate with remote endpoints. -
RestClient
, which provides an interface for sending requests to remote endpoints and receiving responses.
The request builder portion of the framework can be further divided into two layers:
- Built-in request builder classes, which provide generic support for accessing Rest.li resources. The built-in request builders understand how to construct requests for the different Rest.li resource methods, but do not have knowledge of any specific resources or the methods they support. Therefore the built-in request builders cannot validate that a request will be supported by the remote endpoint.
- Type-safe request builder classes, which are generated from the server resource's IDL. The type-safe request builders are tailored to the specific resource methods supported by each resource. The type-safe builders provide an API that guides the developer towards constructing valid requests.
Most developers should work with the type-safe request builders, unless there is a specific need to work with arbitrary resources whose interfaces are unknown at the time the code is written.
h3. Depending on a Service's Client Bindings
Usually, developers building Rest.li services publish Java client bindings for the Rest.li resources their service provides as artifacts into a shared repository such as a maven repo. By adding a dependency to these artifacts, other developers can quickly get their hands on the request builder classes defined in these client bindings to make requests to the resources provided by that service.
To add a dependency from a gradle project, first add the artifact containing the rest client bindings to your dependency list. If you are unsure of the name of the artifact, ask the service owners (they are usually the artifact with a name ending in -client, -api or -rest). Note that the "configuration":http://gradle.org/docs/current/userguide/dependency_management.html#sec:dependency_configurations for the dependency must be set to restClient
:
build.gradle:
...
dependencies {
// for a local project:
compile project(path: ':example-rest-client', configuration: 'restClient')
// for a versioned artifact:
compile group: 'org.somegroup', name: 'some-rest-client', version: '1.0', configuration: 'restClient'
}
...
Then add the dependency to the build.gradle of a module:
build.gradle
...
compile spec.product.example.restClient
...
h3. Depending on Data Templates
To add a dependency to Java bindings for data models add a dataTemplate
configured dependency in your product-spec.json and then add a compile dependency in your build.gradle, e.g.:
product_spec.json
...
"example": {
"restClient": {
"group": "com.linkedin.example",
"name": "data",
"version": "@spec.product.example.version@",
"configuration": "dataTemplate"
},
"version": "0.0.1"
}
...
build.gradle
...
compile spec.product.example.data
...
Note that you should not usually need to add such a dependency when adding a restClient
dependency as the restClient
should bring in the dataTemplate
transitively.
Note: If you are writing pegasus schemas (.pdsc files) and need to add a dependency on other pegasus schemas you need to add a dataModel
dependency:
build.gradle
...
dataModel spec.product.example.data
...
h3. Type-safe Builders
The client-framework includes a code-generation tool that reads the IDL and generates type-safe Java binding for each resource and its supported methods. The bindings are represented as RequestBuilder classes.
h4. Resource Builder Factory
For each resource described in an IDL file, a corresponding builder factory will be generated. For Rest.li version < 1.24.4 the builder factory will be named @Builders@. For Rest.li version >= 1.24.4 the builder factory is named @RequestBuilders@. The factory contains a factory method for each resource method supported by the resource. The factory method returns a request builder object with type-safe bindings for the given method.
Standard CRUD methods are named @create()@, @get()@, @update()@, @partialUpdate()@, @delete()@, and @batchGet()@. Action methods use the name of the action, prefixed by "action", @action()@. Finder methods use the name of the finder, prefixed by "findBy", @findBy()@
An example for a resource named "Greetings" is shown below. Here is the builder factory for Rest.li < 1.24.4:
public class GreetingsBuilders {
public GreetingsBuilders()
public GreetingsBuilders(String primaryResourceName)
public GreetingsCreateBuilder create()
public GreetingsGetBuilder get()
public GreetingsUpdateBuilder update()
public GreetingsPartialUpdateBuilder partialUpdate()
public GreetingsDeleteBuilder delete()
public GreetingsBatchGetBuilder batchGet()
public GreetingsBatchCreateBuilder batchCreate()
public GreetingsBatchUpdateBuilder batchUpdate()
public GreetingsBatchPartialUpdateBuilder batchPartialUpdate()
public GreetingsBatchDeleteBuilder batchDelete()
public GreetingsDoSomeActionBuilder actionSomeAction()
public GreetingsFindBySearchBuilder findBySearch()
}
Here is the builder factory for Rest.li >= 1.24.4:
public class GreetingsRequestBuilders extends BuilderBase {
public GreetingsRequestBuilders()
public GreetingsRequestBuilders(String primaryResourceName)
public GreetingsCreateRequestBuilder create()
public GreetingsGetRequestBuilder get()
public GreetingsUpdateRequestBuilder update()
public GreetingsPartialUpdateRequestBuilder partialUpdate()
public GreetingsDeleteRequestBuilder delete()
public GreetingsBatchGetRequestBuilder batchGet()
public GreetingsBatchCreateRequestBuilder batchCreate()
public GreetingsBatchUpdateRequestBuilder batchUpdate()
public GreetingsBatchPartialUpdateRequestBuilder batchPartialUpdate()
public GreetingsBatchDeleteRequestBuilder batchDelete()
public GreetingsDoSomeActionRequestBuilder actionSomeAction()
public GreetingsFindBySearchRequestBuilder findBySearch()
}
h4. GET Request Builder
The generated GET request builder for a resource is named @GetBuilder@. The generated builder supports the full interface of the built-in @GetRequestBuilder@.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource's ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
e.g., for a parent pathKey named "groupId" of type @Integer@ in the "Contacts" resource, the binding method would be:
public ContactsGetBuilder groupIdKey(Integer key)
h4. BATCH_GET Request Builder
The generated BATCH_GET request builder for a resource is named @BatchGetBuilder@. The generated builder supports the full interface of the built-in @BatchGetRequestBuilder@.
When building requests with @BatchGetRequestBuilder@, use the @buildKV()@ method (@build()@ is deprecated), .e.g:
new FortunesBuilders().batchGet().ids(...).buildKV()
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource's ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
e.g., for a parent pathKey named "groupId" of type @Integer@ in the "Contacts" resource, the binding method would be:
public ContactsBatchGetBuilder groupIdKey(Integer key)
h4. FINDER Request Builder
The generated FINDER request builder for a resource is named @FindByBuilder@. The generated builder supports the full interface of the built-in @FindRequestBuilder@.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource's ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
The generated builder will contain a method to set each of the finder's query parameters, of the form:
public <BuilderType> <paramName>Param(<ParamType> value);
If the finder specifies @AssocKey@ parameters, the builder will contain a method to set each of them, of the form:
public <BuilderType> <assocKeyName>Key(<AssocKeyType> value);
h4. CREATE Request Builder
The generated CREATE request builder for a resource is named @CreateBuilder@. The generated builder supports the full interface of the built-in @CreateRequestBuilder@.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource's ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
h4. BATCH_CREATE Request Builder
The generated BATCH_CREATE request builder for a resource is named @BatchCreateBuilder@. The generated builder supports the full interface of the built-in @BatchCreateRequestBuilder@.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource's ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
h4. PARTIAL_UPDATE Request Builder
The generated PARTIAL_UPDATE request builder for a resource is named @PartialUpdateBuilder@. The generated builder supports the full interface of the built-in @PartialUpdateRequestBuilder@.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource's ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
See "Creating partial updates":https://github.com/linkedin/rest.li/wiki/Rest.li-User-Guide#wiki-creating-partial-updates for details on how to create a request for a partial update.
h4. BATCH_PARTIAL_UPDATE Request Builder
The generated BATCH_PARTIAL_UPDATE request builder for a resource is named @BatchPartialUpdateBuilder@. The generated builder supports the full interface of the built-in @BatchPartialUpdateRequestBuilder@.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource's ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
h4. UPDATE Request Builder
The generated UPDATE request builder for a resource is named @UpdateBuilder@. The generated builder supports the full interface of the built-in @UpdateRequestBuilder@.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource's ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
h4. BATCH_UPDATE Request Builder
The generated BATCH_UPDATE request builder for a resource is named @BatchUpdateBuilder@. The generated builder supports the full interface of the built-in @BatchUpdateRequestBuilder@.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource's ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
h4. DELETE Request Builder
The generated DELETE request builder for a resource is named @DeleteBuilder@. The generated builder supports the full interface of the built-in @DeleteRequestBuilder@.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource's ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
h4. BATCH_DELETE Request Builder
The generated BATCH_DELETE request builder for a resource is named @BatchDeleteBuilder@. The generated builder supports the full interface of the built-in @BatchDeleteRequestBuilder@.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource's ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
h4. ACTION Request Builder
The generated ACTION request builder for a resource is named @DoBuilder@. The generated builder supports the full interface of the built-in @ActionRequestBuilder@.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource's ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
The generated builder will contain a method to set each of the finder's query parameters, of the form:
public <BuilderType> param<ParamName>(<ParamType> value);
h4. Calling Sub-resources
To call a subresource of the fortunes resource, .e.g:
GET /fortunes/1/subresource/100
The parent keys can be specified by calling generated setters on the builder. In this case the @fortunesIdKey()@ method. E.g.,
new SubresourceBuilders().get().fortunesIdKey(1l).id(100l).build()
Parent path keys can also be set directly builder classes using the @setPathKey()@ method on the builders classes. E.g.
.setPathKey("dest", "dest").setPathKey("src", "src")
h3. Built-in Request and RequestBuilder classes
The built-in RequestBuilder classes provide generic support for constructing Rest.li requests. This layer is independent of the IDL for specific resources, and therefore the interface does not enforce that only "valid" requests are constructed.
There is one RequestBuilder subclass for each of the Rest.li resource methods. Each RequestBuilder provides a @.build()@ method that constructs a @Request@ object that can be used to invoke the corresponding resource method. Each RequestBuilder constructs the @Request@ subclass that corresponds to the Rest.li method, e.g., @BatchGetRequestBuilder.build()@ returns a @BatchGetRequest@. The @Request@ subclasses allow framework code to introspect the original type and parameters for a given request.
Each RequestBuilder class supports a subset of the following methods, as appropriate for the corresponding resource method:
- @header(String key, String value)@ - sets a request header.
- @id(K id)@ - sets the entity key for the resource.
- @ids(Collection ids)@ - sets a list of entity keys
- @name(String name)@ - sets the name for a named resource method
- @param(String key, Object value)@ - sets a named parameter
- @reqParam(String key, Object value)@ - sets a required parameter
- @assocKey(String key, Object value)@ - sets an association key parameter
- @pathKey(String key, Object value)@ - sets a path key parameter (entity key of a parent resource)
- @paginate(int start, int count)@ - sets pagination parameters
- @fields(PathSpec... fieldPaths)@ - sets the fields projection mask
- @input(V entity)@ - sets the input payload for the request
- @inputs(Map<K, V> entities)@ - sets the input payloads for batch requests
The following table summarizes the methods supported by each RequestBuilder type.
|. Request Builder |. header |. id |. ids |. name |. param |. reqParam |. assocKey |. pathKey |. paginate |. fields |. input |_. inputs | | Action | - | - | | - | - | | | - | | | | | | Find | - | | | - | - | - | - | - | - | - | | | | Get | - | -* | | | - | - | | - | | - | | | | Create | - | | | | - | - | | - | | | - | | | Delete | - | -* | | | - | - | | - | | | | | | PartialUpdate | - | - | | | - | - | | - | | | - | | | Update | - | -* | | | - | - | | - | | | - | | | BatchGet | - | | - | | - | - | | - | | - | | | | BatchCreate | - | | | | - | - | | - | | | | - | | BatchDelete | - | | - | | - | - | | - | | | | | | BatchPartialUpdate | - | | | | - | - | | - | | | | - | | BatchUpdate | - | | | | - | - | | - | | | | - | *:It is not supported, if the method is defined on a simple resource.
Please refer to the JavaDocs for specific details of RequestBuilder and Request interfaces.
h3. Restspec IDL
Rest.li uses a custom format called Restspec (short for REST Specification) as its interface description language (IDL). The Restspec provides a succinct description of the URI paths, HTTP methods, query parameters, and JSON format which, together, form the interface contract between the server and the client.
Restspec files are a JSON format, and use the file suffix *.restspec.json.
At a high level, the restspec contains the following information:
- name of the resource
- path to the resource
- schema type (value type) of the resource
- resource pattern (collection / simple / association / actionSet)
- name and type of the resource key(s)
- list of supported CRUD methods (CREATE, GET, UPDATE, PARTIAL_UPDATE, DELETE, and corresponding batch methods)
- description of each FINDER, including ** name ** parameter names, types, and optionality ** response metadata type (if applicable)
- description of each ACTION, including ** name ** parameter names, types, and optionality ** response type ** exception types
- a description of each subresource, containing the information described above
Additional details on the Restspec format may be found in the "design documents ":https://github.com/linkedin/rest.li/wiki/Rest.li-.restspec.json-Format. The Restspec format is formally described by the .pdsc schema files in "com.linkedin.restli.restspec.* " distributed in the restli-common module.
h4. IDL Generator Tool
The IDL generator is used to create the language-independent interface description (IDL) from Rest.li resource implementations (annotated java code).
The IDL generator is available as part of the restli-tools JAR, as the @com.linkedin.restli.tools.idlgen.RestLiResourceModelExporterCmdLineApp@ class.
For details on how to use the IDL Generator, see "Gradle build integration":https://github.com/linkedin/rest.li/wiki/Gradle-build-integration.
h3. RestClient
@RestClient@ encapsulates the communication with the remote resource. @RestClient@ accepts a @Request@ object as input, and provides a @Response@ object as output. The @Request@ objects should usually be build using the "generated type-safe client builders":./Rest.li-User-Guide#type-safe-builders. Since the @RestClient@ interface is fundamentally asynchronous, the @Response@ must be obtained through either a @ResponseFuture@ or a @Callback@ (both options are supported).
@RestClient@ is a simple wrapper around an R2 transport client. For standalone / test use cases, the transport client can be obtained directly from R2, e.g., via the @HttpClientFactory@. If you wish to use D2 the @Client@ used by the @RestClient@ must be a D2 client.
The @RestClient@ constructor also requires a URI prefix that is prepended to the URIs generated by the Request Builders. When using D2, a prefix of @"d2://"@ should be provided, which results in URIs using the D2 scheme.
h4. Response API changes in Rest.li >= 1.24.4
Starting with Rest.li 1.24.4 we have introduced a few changes to the @Response@ API.
h5. Response from a BATCH_GET request
When a @BatchGetEntityRequest@ is sent using a @RestClient@ we get back (after calling @sendRequest(...).getResponse().getEntity()@) a @BatchKVResponse<K,EntityResponse>@ where @K@ is the key type and @V@ is the value (which extends @RecordTemplate@) for the resource we are calling.
@EntityResponse@ is a @RecordTemplate@ with three fields:
- @entity@ provides an entity record if the server resource finds a corresponding value for the key;
- @status@ provides an optional status code;
- @error@ provides the error details from the server resource (generally @entity@ and @error@ are mutually exclusive as @null@, but it is ultimately up to the server resource).
Note that since @EntityResponse@ contains an @error@ field, the @Map<K, V>@ returned by @BatchEntityResponse#getResults()@ contains both successful as well as failed entries. @BatchEntityResponse#getErrors()@ will only return failed entries.
h4. ResponseFuture
The @RestClient@ future-based interface returns @ResponseFuture@, which implements the standard @Future@ interface, and extends it with a @getResponse()@ method. The advantage of @getResponse()@ is that it is aware of Rest.li exception semantics, throwing @RemoteInvocationException@ instead of @ExecutionException@.
h4. Error Semantics
The following diagram illustrates the request/response flow for a client/server interaction. The call may fail at any point during this flow, as described below.
!https://github.com/linkedin/rest.li/wiki/RequestFlow.png(Rest.li Request Flow)!
The following list describes the failures scenarios as observed by a client calling @ResponseFuture.getResponse()@
Failure Scenarios
- Client Framework (outbound) ** @ServiceUnavailableException@ - if D2 cannot locate a node for the requested service URI ** @RemoteInvocationException@ - if R2 cannot connect to the remote endpoint or send the request
- Network Transport (outbound) ** @TimeoutException@ - if a network failure prevents the request from reaching the server
- Server Framework (inbound) ** @RestLiResponseException@ - if an error occurs within the framework, resulting in a non-200 response ** @TimeoutException@ - if an error prevents the server from sending a response
- Server Application ** @RestLiResponseException@ - if the application throws an exception the server framework will convert it into a non-200 response ** @TimeoutException@ - if an application error prevents the server from sending a response in a timely manner
- Server Framework (outbound) ** @RestLiResponseException@ - if an error occurs within the framework, resulting in a non-200 response ** @TimeoutException@ - if an error prevents the server from sending a response
- Network Transport (inbound) ** @TimeoutException@ - if a network failure prevents the response from reaching the client
- Client Framework (inbound) ** @RestLiDecodingException@ - if the client framework cannot decode the response document ** @RemoteInvocationException@ - if an error occurs within the client framework while processing the response.
h3. ParSeq integrated Rest client
The @ParSeqRestClient@ wrapper facilitates usage with ParSeq by providing methods that return a @Promise@ or a @Task@. For example, users can create multiple requests and use ParSeq to send them in parallel. This feature is independent of the asynchronous resources; in particular, the server resource does not have to be asynchronous.
ParSeqRestClient client = new ParSeqRestClient(plain rest client);
// send some requests in parallel
Task<Response<?>> task1 = client.createTask(request1);
Task<Response<?>> task2 = client.createTask(request2);
Task<Response<?>> combineResults = ...;
// after we get our parallel requests, combine them
engine.run(Tasks.seq(Tasks.par(task1, task2), combineResults))
Users of @createTask@ are required to instantiate their own ParSeq engine and start the task themselves.
h3. Client Code Generator Tool
As described above, the Rest.li client framework includes a code-generation tool that creates type-safe Request Builder classes based on resource IDL files.
The code generator is available as part of the restli-tools JAR, as @com.linkedin.restli.tools.clientgen.RestRequestBuilderGenerator@. The generator is invoked by providing an output directory and a list of input IDL files as command-line arguments.
In addition, the generator recognizes the following system properties:
- @generator.rest.generate.datatemplates@ - boolean property indicating whether the generator should generate Java RecordTemplate classes for the .pdsc schemas referenced by the IDL file.
- @generator.default.package@ - the default package name for generated classes
- @generator.resolver.path@ - a colon-separated list of filesystem paths to search when resolving references to named schemas. See "Data Template Generator": for more details.
The Rest.li client code generator is integrated as part of the @pegasus@ gradle plugin. For details see "Gradle build integration":https://github.com/linkedin/rest.li/wiki/Gradle-build-integration.
h3. Rest.li-extras
Rest.li can be used with the D2 layer for dynamic discovery and client-side load balancing. The use of D2 is normally transparent at the Rest.li layer. However, for applications wishing to make more sophisticated use of Rest.li and D2, the @restli-extras@ module is provided.
h4. Scatter / Gather
The main feature supported in @restli-extras@ is the ability to make parallel "scatter/gather" requests across all the nodes in a cluster. Currently, scatter/gather functionality is only supported for BATCH_GET methods.
Scatter/gather makes use of D2's support for consistent hashing, to ensure that a given key is routed to the same server node when possible. The @ScatterGatherBuilder@ interface can be used to partition a single large @BatchGetRequest@ into @N@ @BatchGetRequests@, one for each node in the cluster. The key partitioning is done according to the D2 consistent hashing policy, using a @KeyMapper@ object obtained from the D2 @Facilities@ interface. Batch updates and deletes are also supported.
Quick Access:
- Tutorials
- Dynamic Discovery
- Request Response API
-
Rest.li User Guide
- Rest.li Server
- Resources
- Resource Types
- Resource Methods
- Rest.li Client
- Projections
- Filters
- Wire Protocol
- Data Schemas
- Data
- Unstructured Data
- Tools
- FAQ