-
Notifications
You must be signed in to change notification settings - Fork 0
Rest.li User Guide
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 some key architectural elements, and provides an overview of the development process, while 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.
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. Blue arrows indicate the flow of requests out of the client and into the server, red arrows represent the server’s response. Blue boxes represent classes/objects that you implement as a developer, while green boxes are classes/objects that Rest.li generates or otherwise provides for you.
Data and Control Flow Between a Rest.li Server and ClientRest.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 to construct complex asynchronous request flows.
There are several server implementations:
- Servlet, Battle tested and ready for production use. Typically the servlet is deployed in a Jetty container.
- Netty, Experimental
- Embedded Jetty, primarily for integration testing as it’s trivial to spin up as part of a test suite
The remainder of this guide will assume use of the servlet server implementation.
Starting with the server (on the left 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 parses the request to extract any parameters
- Rest.li creates a new instance of the resource class designated to handle the request
- Rest.li invokes the appropriate methods of the resource object, passing in any necessary Java parameters
- The resource object instantiates and returns a response, in the form of a RecordTemplate object
- 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 below).
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.
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. 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.
- 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 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.
In this chapter, we will look at Rest.li support for implementing servers.
The first step in building a Rest.li application is to define your data schema using Pegasus Data Schemas. 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 for full details.
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 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.
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.
@RestLiCollection(name = "fortunes", namespace = "com.example.fortune")
public class FortunesResource extends CollectionResourceTemplate<Long, Fortune>
{
@Override
public Fortune get(Long key)
{
// retrieve data and return a Fortune object ...
}
}
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.
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 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 |
Association | @RestLiAssociation | Implement AssociationResource , extend AssociationResourceTemplate , or implement KeyValueResource for use cases requiring extensive customization |
Actions | @RestLiActions | N/A |
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 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 “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.
Examples:
@RestLiCollection(name = "fortunes", namespace = "com.example.fortune", keyName = "fortuneId")
public class FortunesResource extends CollectionResourceTemplate<Long, Fortune>
{
...
}
@RestLiCollection(name = "subresource", namespace = "com.example.fortune", parent = FortunesResource.class)
public class SubResource extends CollectionResourceTemplate<Long, SubResourceEntity>
{
public SubResourceEntity get(Long subresourceKey)
{
Long parentId = getContext().getPathKeys().getAsLong("fortuneId");
}
...
}
A variation of a Collection resource implementation can make use of an arbitrarily complex key type, consisting of Key and Parameters parts, each extending RecordTemplate and, like Rest.li resources, ar generated from the pdsc schema. The implementation of such a 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.
It follows that to implement such a collection, one needs to define pdsc schemas for Key and Key Parameter, and generate corresponding RecordTemplate-derived classes.
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 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. -
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.
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.
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.
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 | Association | Action Set |
---|---|---|---|
GET / BATCH_GET / GET_ALL | x | x | |
FINDER | x | x | |
CREATE / BATCH_CREATE | x | ||
UPDATE / BATCH_UPDATE | x | x | |
PARTIAL_UPDATE / BATCH_PARTIAL_UPDATE | x | x | |
DELETE / BATCH_DELETE | x | x | |
ACTION | 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
.
The GET resource method is intended to retrieve a single entity representation based upon its key. 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 the following method signature:
public V get(K key);
As of version 1.5.8, GET supports a method signature with a wrapper return type:
@RestMethod.Get
public GetResult<V> getWithStatus(K key);
The return type
GetResult<V>
allows users to set an arbitrary HTTP status code for the response. For more information about the RestMethod.Get
annotation, see Free-form Resources.
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.
Resources providing the BATCH_GET resource method must override the following method signature:
public Map<K, V> batchGet(Set<K> ids);
Resources may also return BatchResult
, which allows errors to be returned along with entities that were successfully retrieved.
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);
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 ctx,
@QueryParam("buyerId") Integer buyerId,
@QueryParam("buyerType") Integer buyerType,
@QueryParam("orderId") Integer orderId)
throws InternalException
{
...
}
...
Finder methods must return either:
List<V>
CollectionResult<V, MetaData>
-
BasicCollectionResult<V>
, a subclass ofCollectionResult
- 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 typePagingContext
. -
@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. -
@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 theassocKeys
field of the@RestLiAssociation
annotation.
Parameters marked with @QueryParam
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
@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);
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 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
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.
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.
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.
Resources providing the UPDATE resource method must override the following method signature:
public UpdateResponse update(K key, V entity);
The returned UpdateResponse
object indicates the HTTP status code to be returned.
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.
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!"}}}'
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.
DELETE methods are intended to model deleting (removing) an entity with a given key. DELETE has side effects, but is idempotent.
Resources providing the DELETE resource method must override the following method signature:
public UpdateResponse delete(K key);
The returned UpdateResponse
object indicates the HTTP status code to be returned.
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.
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 toResourceLevel.COLLECTION
, which indicates that the action does not support an entity key as a URI parameter.ResourceLevel.ENTITY
indicates that the action requires 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 ofRecordTemplate
-
FixedTemplate
or a subclass ofFixedTemplate
-
AbstractArrayTemplate
or a subclass ofAbstractArrayTemplate
, e.g.,StringArray
,LongArray
, etc. -
AbstractMapTemplate
or a subclass ofAbstractMapTemplate
, e.g.,StringMap
,LongMap
, etc.
Similar to GetResult<V>
, since 1.5.8, Rest.li supports an ActionResult<V>
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)
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.
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 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);
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 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);
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 implement a free-form resource by implementing the KeyValueResource
marker interface and annotating CRUD methods with @RestMethod.*
annotations.
public class FreeFormResource 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.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);
}
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
.
{info:title=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 the
KeyValueResource
marker interface - 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.
{info}
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.
If a resource method returns null
, the framework will automatically generate a 404
response to be sent to the client.
CreateResponse
and UpdateResponse
allow an HTTP status code to be provided. Status codes in the 4xx
and 5xx
ranges may be used to report errors.
The framework defines a special exception class, RestLiServiceException
, which contains an HTTP status 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.
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.
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);
}
Rest.li provides built-in support for field projection, i.e., structural filtering of responses. The Rest.li server framework recognizes the "fields"
query parameter in the request, and parses its value as a MaskTree
. The resulting MaskTree
is available through the ResourceContext
(see above). The projection MaskTree
is also automatically applied to the response object before it is transmitted. 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.
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
}
}
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 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.
Rest.li allows resources to return results asynchronously through a ParSeq 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
}
Furthermore, a
Promise
based method can take a ParSeq context as an argument.
@RestMethod.Get
public Promise<Greeting> get(final Long a, @ParSeqContext final Context psContext)
{
psContext.run(some task, some other task);
psContext.after(prereq).run(dependency);
// return a promise that your task completes
}
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.
As there are currently no asynchronous templates, all asynchronous methods must be annotated with the appropriate Rest.li annotation. If desired, the synchronous resource templates may be mixed with annotated asynchronous methods. Note that the Promise
and Task
styles have method signatures identical to the synchronous methods, so a different method name should be used.
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.
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 resourcehttp://<host>:<port>/<context-path>/<resource>
isGET http://<host>:<port>/<context-path>/restli/docs/<resource>
(GET
is the HTTP GET method, which is the default for web browser). The root URL 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<context-path>
part if there is no context path. -
JSON. There are 2 alternative ways to access the raw JSON data:
- Use the
format=json
query parameter on any of the HTML pages above. For example,GET http://<host>:<port>/<context-path>/restli/docs/rest/<resource>?format=json
for resource documentation andGET http://<host>:<port>/<context-path>/restli/docs/data/<full_name_of_data_schema>?format=json
for schema documentation. HomepageGET http://<host>:<port>/<context-path>/restli/docs/?format=json
is also available, which aggregates all resources and data schemas. - Use the HTTP OPTIONS method. 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 specialrestli/docs/
path. For example,OPTIONS http://<host>:<port>/<context-path>/<resource>
.
- Use the
The JSON format is structured as following:
{
"models": {
"<full_name_of_data_schema_1>": { <pdsc_of_data_schema_1> },
"<full_name_of_data_schema_2>": { <pdsc_of_data_schema_2> }
}
"resources": {
"<resource_1>": { <idl_of_resource_1> },
"<resource_2>": { <idl_of_resource_2> }
}
}
When accessing the JSON format of data schema, the resources
key exists but the value is always empty.
-
documentationRequestHandler
: instance ofRestLiDocumentationRequestHandler
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 byDefaultDocumentationRequestHandler
to properly generate links. Usually this should be an absolute path.
The Rest.li client framework provides support for accessing resources defined using Rest.li. The client framework consists of two parts:
- Request Builder 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.
- Rest Client classes, which provide 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.
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” 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
...
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, then the build.gradle dependency should be:
build.gradle
...
dataModel spec.product.example.data
...
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.
For each resource described in an IDL file, a corresponding builder factory will be generated, named <Resource>Builders
. 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<ActionName>()
. Finder methods use the name of the finder, prefixed by “findBy”, findBy<FinderName>()
An example for a resource named “Greetings” is shown below:
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 FindRequestBuilder<Long, com.linkedin.restli.examples.greetings.api.Greeting> find()
public GreetingsFindBySearchBuilder findBySearch()
}
The generated GET request builder for a resource is named <Resource>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)
The generated BATCH_GET request builder for a resource is named <Resource>BatchGetBuilder
. The generated builder supports the full interface of the built-in BatchGetRequestBuilder
.
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)
The generated FINDER request builder for a resource is named <Resource>FindBy<FinderName>Builder
. 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);
The generated CREATE request builder for a resource is named <Resource>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);
The generated BATCH_CREATE request builder for a resource is named <Resource>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);
The generated PARTIAL_UPDATE request builder for a resource is named <Resource>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);
The generated BATCH_PARTIAL_UPDATE request builder for a resource is named <Resource>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);
The generated UPDATE request builder for a resource is named <Resource>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);
The generated BATCH_UPDATE request builder for a resource is named <Resource>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);
The generated DELETE request builder for a resource is named <Resource>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);
The generated BATCH_DELETE request builder for a resource is named <Resource>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);
The generated ACTION request builder for a resource is named <Resource>Do<ActionName>Builder
. 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);
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<K> 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 | – | – | – | – | – |
Please refer to the JavaDocs for specific details of RequestBuilder and Request interfaces.
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 / 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.
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.
RestClient
encapsulates the communication with the remote resource. RestClient
accepts a Request
object as input, and provides a Response
object and output. Since the RestClient
interface is fundamentally asynchronous, the Response
must be obtained through either a future 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
. However, in most LinkedIn use cases, application code should use a client adapter that is D2 aware. The most common mechanism to obtain a D2-aware client is to create a bean that implements the ClientAware
interface, which triggers automatic injection of the appropriate Client
instance.
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.
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
.
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.
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.
-
Experimental feature
The ParSeq REST client was added in pegasus 1.20 and is subject to change.
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.
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.
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.
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.
- Dynamic Discovery (D2)
- Data Schema and Templates
-
Rest.li
- Server
- Client
- Projections
- Tools
- FAQs