-
Notifications
You must be signed in to change notification settings - Fork 0
Modeling Resources with Rest.li
This page will help you model your resource using Rest.li. Before reading this page, you may want to get a feel for how to write and build code with Rest.li by reading the "quick start guide ":REST – rest.li QuickStart Guide.
Rest.li is intended to foster the design of Uniform Interfaces. The idea of Uniform Interfaces is core to REST because it establishes standard patterns and behaviors that allow clients to explore and make sense of interfaces which they have never seen before.
Rest.li’s approach to uniform interface is based on REST principles, and comprises the following:
- Resource Identifiers
- Self-descriptive Messages
- Resource Methods
- Resource Types
Every resource has an identifier, a name by which it can be accessed. Rest.li resource identifiers are URIs which follow specific patterns.
Rest.li messages are built with a JSON-encoded body, which provides a self-describing structure for the data in the message. The Rest.li framework provides convenient access to messages using Pegasus RecordTemplates.
Since Rest.li is built on HTTP, messages may also contain metadata in HTTP headers.
Resources are the nouns in the Rest.li world, and Resource Methods are the verbs. Rest.li provides a standard set of Resource Methods which describe what clients may do with resources. These verbs are different than the HTTP verbs (GET, POST, PUT, DELETE…) but map onto them (CREATE is a POST, UPDATE is a PUT, FIND is a GET).
- CREATE is used to create an entity. Create requires that the resource identifier is assigned by the server. (HTTP verb: POST)
- GET is used to read an entity. Get requires the client to provide the resource identifier. (HTTP verb: GET)
- UPDATE is used to modify an entity. Update requires the client to provide the resource identifier. (HTTP verb: PUT)
- DELETE is used to remove an entity. Delete requires the client to provide the resource identifier. (HTTP verb: DELETE)
- FIND is used to search for entities. Find allows the client to provide query parameters for the search. Find methods return a list of entities. (HTTP verb: GET)
CREATE, GET, UPDATE and DELETE also have a corresponding batch method, e.g. BATCH_GET is used to read multiple entities and requires the client to provide a list of resource identifiers, and returns a map from identifiers to entities.
Each Rest.li resource endpoint is one of the following types: Collection, Association. Additionally, each resource endpoint may be a subresource of any other resource.
Collection is the most frequently used resource type. A collection models a key/value map of entities. It may be helpful to think of a database table with a primary key. Collections have a key type, used for identifying entities in the collection, and a value type used to represent the entity itself.
Collections can support all of the Rest.li resource methods described above.
Collections are declared by creating a class that extends CollectionResourceTemplate (or ComplexKeyResourceTemplate for complex keys, see below for details).
For example,
@RestLiCollection(name="items", ...)
public class ItemsResource extends CollectionResourceTemplate<Long, Item>
Defines a child resource with a URI of the form:
/items/{longKey}
Associations are structured like specialized collections, but are used for a different modeling purpose. Associations model relationships between entities. Associations are like mapping tables in a database. Associations have a compound key consisting of multiple partial keys. Each partial key references one of the associated entities. Like collections, associations have a value type. However, in the case of associations, the value type is used to model attributes on the relationship between entities.
Associations support all of the Rest.li resource methods except for Create. Create requires the server to assign the resource identifier, which is incompatible with the constraint that an association’s partial keys are “foreign” keys from the referenced entities. Instead of using Create, new association relationships are made by using Update, which allows the client to provide the resource identifier.
For example,
@RestLiAssociation(name="myRelations", assocKeys={@Key(name="key1", type=long.class), @Key(name="key2", type=long.class)}, ...)
public class MyRelationResource extends AssociationResourceTemplate<Relation> { ... }
Defines a child resource with a URI of the form:
/myRelations/key1={longKey1}&key1={longKey2}
Child resources are resources which are referenced through a parent resource. In Rest.li, the resource identifier for a child resource is an extension of the resource identifier for the parent resource. This means that the child resource has access to all of the information used to access the parent resource, including entity keys. A common pattern for using child resources is when accessing an element of a collection requires the key of another collection (the parent resource).
For example,
@RestLiCollection(parent=MyParentResource.class, name="mySubResources", ...)
public class MySubResource extends CollectionResourceTemplate<String, MySub> { ... }
Defines a child resource with a URI of the form:
/myParentResources/{parentResourceKey}/mySubResources/{stringKey}
Rest.li Resources may be customized to handle a variety of use cases. The most common means of customization is to only implement the methods appropriate for a particular use case. Rest.li allows you to implement as many or as few of the resource methods as you choose. The framework will understand which methods you have implemented, and will advertise only those methods to clients.
Here are some examples:
A read-only collection can be used to expose entities which you do not want your clients to be able to modify. This might be useful if you need to expose a view which is derived from other data, or if you need to expose entities whose lifecycle is managed internally by your domain.
Read-Only collections can easily be implemented using Rest.li Collections, by only including read methods such as GET, BATCH_GET and FIND. By omitting any write operations (CREATE, UPDATE, DELETE) the collection becomes read-only.
Another variation on collections is a “natural key” collection, where the identifier for each entity is one of the domain attributes of the entity itself. (The alternative is a synthetic key collection, where the identifier is assigned arbitrarily, such as from a sequence number).
Natural-key collections are implemented in Rest.li using Collections, by omitting the implementation for CREATE, and instead using UPDATE to add entities into the Collection. This is because CREATE is used when the server assigns the key for the entity. Although the name may be counter-intuitive, using UPDATE for this case is correct, because it is the resource method with the best defined semantics (PUT this entity representation at this location).
By extending ComplexKeyResourceTemplate instead of CollectionResourceTemplate, a collection may use any complex type (any defined pegasus record type) as a key.
A “Factory” Resource is used when the representation of an entity is assigned by the server using some input information provided by the client. For example, an authentication service might create a Session entity given a username and password, but the Session entity is created internally by the service.
To create a Factory resource in rest.li, use a Collection which only implements CREATE (and optionally DELETE). The value type of the Collection should be a Pegasus schema representing the input to the factory method. The key type should be the key for the resulting entity.
Note that a Factory would normally be paired with a Collection (perhaps a Read-Only Collection) which provides access to the entities themselves.
- Start with nouns (“things”) in your domain. These will be your resources and entity representations.
- Think about the keys for each entity — what is the minimal information you need in order to GET (read) an entity?
- For any entity that can be accessed with a single key, try creating a root-level Collection resource. Implement as many of the Rest.li methods as you reasonably can.
- For entities that require multiple keys to access,
- If all but one of the keys belongs to other resources, try modeling as a child resource of another entity.
- If all of the keys belong to other resources, consider whether your entity is really a relationship between the other entities. If so, try creating an association.
- If all of the keys are unique to this entity, you may have a collection with a compound key. Rest.li does not currently support compound keys for collections, but you can model your resource as a collection with a String key, and compose the key yourself.
Rest.li collections have a single value type which is used for all of the resource methods supported by the collection. There are cases where you want to use different entity representations with the same key, e.g., the input to CREATE is a different type than that returned by GET (See Factories section above). In such cases you have two options:
- Use a union in the Pegasus schema for your value type.
- Create a different resource for each value type
Sometimes the same entity can be accessed using different sets of keys.
For example, Products belong to Companies, and are normally accessed efficiently using both the CompanyId and ProductId, so the Product collection is a child resource of the Company collection. But if there is a legacy access path which uses only ProductId, this method cannot be modeled on the Product resource, because the client does not have a key for the parent Company.
In such cases, you have a few options:
- Create a separate resource for each access path
- Use FIND methods for some or all of the access paths
A frequently encountered question is how relationships between entities should be modeled. Suppose you have two objects, A and B – should you model their relationship by:
- Creating an association between A and B?
- Making B a subresource of A?
- Making B a field of A?
Here are some rules of thumb to help answer this question:
Associations are preferred when:
- A and B have independent lifecycles, i.e., they can both exist without needing to be related
- A and B have a many-to-many / bidirectional relationship (you can lookup in either direction)
- You need to store attributes on the relationship itself
- You need to relate more than two objects, e.g., A, B, and C
- There is more than one type of relationship between A and B
Fields are preferred (B is a field of A) when:
- B exists only as a part (fragment) of A. A is not complete without B.
- B does not have its own independent key
- B models a value, not an entity, i.e., identity is not important, only the values of attributes matter for equality.
- There are a small number of B objects related to any given A object, such that they can all be accessed together.
- B is usually accessed and modified as part of A
Subresources are preferred (B is a subresource of A) when:
- B has its own key, which is unique within the scope of A.
- A’s key is required to access B
- B is frequently accessed or modified independent of A
- B is queried as a collection, e.g., using filters, sorts, or pagination
- B models an entity that is dependent on A, but which is not a part of A
- Each B relates to exactly one A
- Remember that it’s ok to have multiple separate Resources with the same Pegasus schema as value type, as long as each resource represents a distinct pattern of interaction. Likewise, it’s ok to have separate Resources which access the same underlying implementation.
- Resource identifiers are the key (pun intended) to parent/child resource relationships. A resource should be made a child resource if and only if it depends on the parent resource’s key. If a resource can be accessed without providing the parent’s key, it should not be a child.
- Use separate resources for different representations of the same underlying implementation. If you want to provide more than one representation (Pegasus schema) of the same information, you can create a resource for each representation. However, if one representation is just a subset of the other, you should use Rest.li’s field projection support.
Uniform Interfaces give us a powerful way to expose resources to the broadest possible set of clients. However, if you have a requirement which can’t reasonably be modeled using the uniform interface constructs, Rest.li does provide a loophole to help you. Actions are a special type of resource method which allow arbitrary RecordTemplate types as input and output parameters, and which have no constraints on semantics. It should be possible to model any operation as an Action, allowing you to fit your special case within Rest.li. However, because Actions do not conform to the Uniform Interface, actions cannot easily be used by higher-level frameworks like query languages, etc., which may be built on top of Rest.li. You should therefore avoid Actions whenever there is another reasonable option.
- Dynamic Discovery (D2)
- Data Schema and Templates
-
Rest.li
- Server
- Client
- Projections
- Tools
- FAQs