Say I want to model the relationship of IP Address and ports in a RESTful manner. An IP address contains many ports, while a port can only have one IP Address. Additionally an IP Address is unique in this domain by it's address, while a port is unique by it's port number within it's IP address.
What are the potential strategies to model this? I can think of three that all have advantages and disadvantages.
Separate Resources
Maintain completely separate resources of the two and represent the relationship through the URL structure and allow discovery through links.
GET .../ipaddress/{address}/
{
"ipaddress": "1.2.3.4",
"ipversion": "v4",
"links": [
{
"rel": "port"
"link": "/ipaddress/1.2.3.4/ports/1234"
},
{
"rel": "port"
"link": "/ipaddress/1.2.3.4/ports/1235"
},
{
"rel": "port"
"link": "/ipaddress/1.2.3.4/ports/1236"
}
]
}
GET .../ipaddress/{address}/port/{portnumber}
{
"port":1234,
"protocol":"unknown"
...other data here
}
Advantages
Logical representation of the resources that maps to the problem precisely.
Allows HTTP methods such as GET/POST/PUT/DELETE to be applied to not only an IP Address, but to a port within an IP Address.
Disadvantages
To retrieve all ports for an IP Address, I have to issue N + 1 requests. One request for the IP address to obtain links to all the ports and then N subsequent requests for each port resource.
Embedded Resources
Embed all port objects within the IP Address object.
GET .../ipaddress/{id}/
{
"ipaddress": "1.2.3.4",
"ipversion": "v4",
"ports": [
{
"port": 1234,
"protocol": "unknown",
otherdata....
},
{
"port": 1235,
"protocol": "unknown"
otherdata....
},
{
"port": 1234,
"protocol": "unknown"
otherdata....
}
]
}
Advantages
Can retrieve all port objects for an IP address with a single request.
Disadvantages
Can no longer issue a separate request to manipulate a port object. For example, how would you delete a single port from an IPAddress? I could issue a PUT request replacing the entire IP Address with the port removed or do some kind of delta but that seems clumsy.
Could potentially lead to a large set of data, say for example if the port object contained additional information about the application that opened it.
Hybrid Approach
Create separate resources, but within the link object in ipaddress, embed some data from that resource, in this case the port number.
GET .../ipaddress/{id}
{
"ipaddress": "1.2.3.4",
"ipversion": "v4",
"links": [
{
"port": 1234,
"rel": "port",
"link": "/ipaddress/1.2.3.4/ports/3"
},
{
"port": 1235,
"rel": "port",
"link": "/ipaddress/1.2.3.4/ports/3"
},
{
"port": 1236,
"rel": "port",
"link": "/ipaddress/1.2.3.4/ports/3"
}
] }
GET .../ipaddress/{id}/port/{portnumber}
Remains the same as the first example.
Advantages
Allows for some data to be obtained in a single request.
Allows me to issue GET/PUT/POST/DEL requires to a URI that represents a port.
Disadvantages
I have explicitly chosen what data to represent in the "link summary". If the client wants to know a list of all protocols for all ports they still have to perform N+1 requests.
I believe things like JSON+HAL use the hybrid approach, in that they have embedded links to other resources that contain data. But does the client have a choice of what is displayed?
If I don't use any embedded data I end up with too many requests and if I embed everything I end up without the fine grained control of being able to manipulate sub-resources. Are there any other modelling alternatives?
I know this is quite a trivial example as port doesn't have a lot of data in it. But this could be applied to any two objects with a dependent relationship.
Thanks
Use HAL+JSON embedded resources with their own link and a field filter in the queryString.
GET /ipaddress/1.2.3.4?fields="ipaddress,inversion,ports:[port,protocol,otherdata]"
{
"ipaddress": "1.2.3.4",
"ipversion": "v4",
"_embedded": {
"ports": {
"items": [
{
"port": 1234,
"protocol": "unknown",
otherdata....
"_links": {
"self": {
"href": "/ipaddress/1.2.3.4/ports/1234"
}
}
},
{
"port": 1235,
"protocol": "unknown"
otherdata....
"_links": {
"self": {
"href": "/ipaddress/1.2.3.4/ports/1235"
}
}
},
...
],
"_links": {
"self": {
"href": "/ipaddress/1.2.3.4/ports"
}
}
}
},
"_links": {
"self": {
"href": "/ipaddress/1.2.3.4"
}
}
}
Btw. the current implementation depends only on your needs...
Yes the client has a choice, you can add metadata (like link relations, microdata, microformats etc...) to your media format, and you can ignore parts of the response. You can send back complex link with the results which can contain input fields and you can setup with them link parameters, like field filters in the queryString, etc... Yes, this will make implementation harder, you have to work with it a lot, but I guess you don't expect a complete code without any effort...
There are a lot of other media types, for example: JSON-LD, collection+json, siren, etc... you can check them if you want, maybe their approaches are better for you...
Related
The following example is given on the json-ld playground.
Person example(expanded):
json-ld:
{
"#context": "http://schema.org/",
"#type": "Person",
"name": "Jane Doe",
"jobTitle": "Professor",
"telephone": "(425) 123-4567",
"url": "http://www.janedoe.com"
}
after serializtion to expanded:
[
{
"#type": [
"http://schema.org/Person"
],
"http://schema.org/jobTitle": [
{
"#value": "Professor"
}
],
"http://schema.org/name": [
{
"#value": "Jane Doe"
}
],
"http://schema.org/telephone": [
{
"#value": "(425) 123-4567"
}
],
"http://schema.org/url": [
{
"#id": "http://www.janedoe.com"
}
]
}
]
I ask myself where does the serializer gets the information to map the properties to the right subsequent schema (name). To achieve that, it must be able to get hold on to the person json ld schema. But if I go to https://schema.org/Person I get an HTML back and not a JSON-LD file.
So where does the serialization knowledge come from?
Jay is correct that the knowledge comes from the #context. This can be specified in a couple of ways:
Inline, using an object value for #context (or an array, which includes an object),
By directly retrieving a context from the URL specified (e.g., https://json-ld.org/contexts/person.jsonld),
By having the server do context-negotiation on the request, as an HTTP request includes an Accept header preferring JSON-LD (see Interpreting JSON as JSON-LD) such as the following:
GET /ordinary-json-document.json HTTP/1.1
Host: example.com
Accept: application/ld+json,application/json,*/*;q=0.1
Or, as is presently deployed by schema.org, by returning a Link header along with a GET or HEAD request identifying the location of the actual context to load (See Alternate Document Location):
HTTP/1.1 200 OK
...
Content-Type: text/html
Link: <alternate.jsonld>; rel="alternate"; type="application/ld+json"
This last case is used by schema.org because of the challenges of making HTTP Content-Negotiation work properly on certain static site generators. If you to a HEAD request at https://schema.org, you'll get back headers including the following:
HTTP/2 200
link: </docs/jsonldcontext.jsonld>; rel="alternate"; type="application/ld+json"
A conforming JSON-LD processor (such as on the json-ld.org playground) knows to follow this link to find the actual context.
In the case of the Person example, "Person" and the other keys are turned into IRIs based on instructions in that context file such as the following:
{
"#context": {
...
"schema": "http://schema.org/",
"Person": {"#id": "schema:Person"},
"name": { "#id": "schema:name"},
"jobTitle": { "#id": "schema:jobTitle"},
"telephone": { "#id": "schema:telephone"},
"url": { "#id": "schema:url", "#type": "#id"},
...
}
}
Note that in the case of "url", it also knows that the value of that property should be treated as an IRI, rather than a text string.
I am providing access to a REST endpoint where some aggregates are computed.
Let's say the model is as follows:
Purchase:
amount: amount spent
group: one of ELECTRONICS, FOOD, FURNITURE, ...
date: purchase date
Now I want to offer a timeline of purchases, aggregated on a weekly basis, and partitioned by group. This will produce the following data:
{
"ELECTRONICS": [{"week": "2018WK1", "amount": 1000.0}, ...],
"FOOD": [{"week": "2018WK1", "amount": 2000.0}, ...],
"FURNITURE": [{"week": "2018WK1", "amount": 3000.0}, ...],
...
}
Some things to note:
the number of groups is not known in advance (it depends on the stored data)
since this is computed data, I would like to return the whole thing in one single request, instead of having to do the aggregates for each of the groups in separate requests
The URL for the request would be something like: /api/weekly_purchases/2018
How can I offer these kind of resources in a REST API?
Return several resources in REST call
How would you do it as a web page?
Somewhere on your web site would be a link, with text "this week's summary" (or whatever the appropriate concept is in the language of your domain). If a user clicked that link, the browser would do a GET on one URL, which would go to the server, aggregate all of the data together, and return the result.
So do that?
REST doesn't care about the spelling of the URI (the browser doesn't try to interpret the URL, except in very shallow generic ways), so /api/weekly_purchases/2018 is fine.
The trick is recognizing that a report summarizing purchases in the current fiscal year, broken out by week, is a resource. It may have data in it that duplicates the information in other resources, even data in many other resources, but it is still a resource itself.
As already mentioned in my initial comment or by VoiceOfUnreason the same techniques that apply to the browser-based Web apply to any interaction model used by applications that follow the REST architecture principles. As also mentioned by VoiceOfUnreason a client would initially request some state returned from the entry-point, i.e. https://api.acme.com that will return a collection of links a client can use to progress its task. In order for the client to determine which URL to invoke the response should give the URI a meaningful name (link-relation name). IANA maintains a list of already specified link-relation names you should use if possible or define your own one in further standards. According to Fielding the specification of media-types and link relations is one of the most important things to do if developing a RESTful architecture.
For simplicity I use a simplified HAL-JSON syntax throughout the example.
{
...
"_links": {
"self": {
"href": "https://api.acme.com"
},
...
"archives": {
"href": "https://api.acme.com/weekly_purchases"
},
...
}
}
According to the HTML 5 spec archives
indicates that the referenced document describes a collection of records, documents, or other materials of historical interest
The link relation name therefore describes the intent of the URI which client can use if interested in retrieve a collection of historical entries. The client does not really have to know the exact URI as he will learn it by simply following the link relation's target href element. This allows the server to change its internal URI structure anytime it has to without actually breaking clients.
On following the archives target URI a client will not really know yet how the actual data has to be retrieved as the URI and the link relation name are to generic. But the server will guide the client through its task. A response on the invocation of the abovementioned target URI might return the following content:
{
"year": [
"2018": {
"_links": {
"chapter": {
"href": "https://api.acme.com/weekly_purchases/2018"
}
}
},
"2017": {
"_links": {
"chapter": {
"href": "https://api.acme.com/weekly_purchases/2017"
}
}
},
...
"2014": {
"_links": {
"chapter": {
"href": "https://api.acme.com/weekly_purchases/2014"
}
}
}
],
"_links": {
"self": {
"href": "https://api.acme.com/weekly_purchases"
},
"first": {
"href": "https://api.acme.com/weekly_purchases"
},
"next": {
"href": "https://api.acme.com/weekly_purchases?p=1"
},
"last": {
"href": "https://api.acme.com/weekly_purchases?p=3"
},
"current": {
"href": "https://api.acme.com/weekly_purchases"
}
}
}
This response basically only teaches the client that there are multiple years available to choose from and the client has to decide which year s/he is interested in an invoke that URI to proceed its task. The next, last and first link relation indicate that there are multiple pages available as only the 5 years per page are returned. The current link relation name will always point to the most recent entry in the collection, which is the initial page (or first page) of the collection-resource. Note further how multiple different link-relation names may point to the same URI. Sometimes it isn't really clear which link relation names to use as their semantics partly overlap. This is just an example on what can be done with link relation names.
A client can now further drill down to the purchases done in 2018 by following the chapter link for 2018. A response on invoking that URI may now look like this:
{
"purchase": [
"W1": {
"sum": 1263.59,
"currency": "Euro",
"_links": {
"about": {
"href": "https://api.acme.com/weekly_purchases/2018/1"
}
}
},
"W2": {
"sum": 569.32,
"currency": "Euro",
"_links": {
"about": {
"href": "https://api.acme.com/weekly_purchases/2018/2"
}
}
},
...
"W48": {
"sum": 72.98,
"currency": "Euro",
"_links": {
"about": {
"href": "https://api.acme.com/weekly_purchases/2018/48"
}
}
},
"current": {
"sum": 72.98,
"currency": "Euro",
"_links": {
"about": {
"href": "https://api.acme.com/weekly_purchases/2018/48"
}
}
}
],
"_links": {
"index": {
"href": "https://api.acme.com/weekly_purchases"
},
"self": {
"href": "https://api.acme.com/weekly_purchases/2018"
},
"current": {
"href": "https://api.acme.com/weekly_purchases/2018"
},
"prev": {
"href": "https://api.acme.com/weekly_purchases/2017"
},
"prev-archive": {
"href": "https://api.acme.com/weekly_purchases/2017"
},
"first": {
"href": "https://api.acme.com/weekly_purchases/2000"
}
}
}
You could either add content here to the weekly summary or hide it down the road by following the about link only if clients are really interested in such details.
Note further: As weekly_purchases is just a string without meaning to the client it does not really know what it means. You could therefore also rename it to purchase-archive or something like that and introduce a further choice to the client and let the client determine whether it wants a weekly, monthly or total summary of that year.
REST is about providing choices to a client and teach it what the actual choices are intended for. One of the aims the RESTful architecture tries to solve is the strict coupling between clients and servers which prevent the latter one from evolving freely and the former ones to break if the latter one changes unexpectedly. This decoupling only works if certain standards are used to increase the likelihood for interoperability. Usually out-of-band information (pre-existing knowledge about the API and how to interact with it) is leading to a coupling. Even Fielding stated that some prior knowledge is needed though but not encoded directly into the application but on reusing certain standards like well-defined and stable media-types and link-relation names.
As per the RIPE REST API documentation, one needs to specify the requests in the following format:
http://rest.db.ripe.net/{source}/{objecttype}/{key}
So I am assuming that looking up an IP address will be like this:
http://rest.db.ripe.net/ripe/inetnum/193.0.6.142.json
However, the response I get is :
{
"link": {
"type": "locator",
"href": "http://rest.db.ripe.net/ripe/inetnum/193.0.6.142"
},
"errormessages": {
"errormessage": [
{
"severity": "Error",
"text": "ERROR:101: no entries found\n\nNo entries found in source %s.\n",
"args": [
{
"value": "RIPE"
}
]
}
]
},
"terms-and-conditions": {
"type": "locator",
"href": "http://www.ripe.net/db/support/db-terms-conditions.pdf"
}
}
What am I doing wrong ?
You are using the wrong URL, the correct URL for your example query would be:
http://rest.db.ripe.net/search.json?query-string=193.0.0.0/21&flags=no-filtering
Or this for XML:
http://rest.db.ripe.net/search.xml?query-string=193.0.0.0/21&flags=no-filtering
Looks like https://rest.db.ripe.net/search.json?query-string=193.0.6.142 is the correct link to use. This seems to return back the same data as I see on ripe.net
You didn't write {key} part right. Inetnum objects on RIPE have "193.0.0.0 - 193.0.7.255" type of key. You must make a request like this:
https://rest.db.ripe.net/ripe/inetnum/91.123.16.0 - 91.123.31.255
I create my Instance on the CLOUD but when try to do a POST the data are not send to the VM, something is wrong with the data I use ?
I'm using Rest Client on Firefox.
This is the body of the code (Json) :
{
"contextElements": [
{
"type": "Room",
"isPattern": "false",
"id": "Room1",
"attributes": [
{
"name": "temperature",
"type": "float",
"value": "23"
},
{
"name": "pressure",
"type": "integer",
"value": "720"
}
]
}
],
"updateAction": "APPEND"
}
The URL is http://10.0.22x.6x:1026/NGSI10/updateContext and the headers are:
Content-Type: application/json
Accept: application/json
Note that you are sending your REST request to a private IP (10.0.22x.6x). However, I guess that you run your Firefox REST Client in a PC or laptop computer without direct connectivity to that IP.
The solution would be to allocate a public IP to the VM, then access to that public IP from your external REST Client. Note that you need the port 1026 opened in the security group associated to that VM (otherwise the cloud will block any attemp to connect to it from an external host).
So I have a REST API in which I Have a resource in which other resources are linked to (related models, in programming point of view).
So how I am doing it right now is that whenever I request for a resource, related resources are referenced via URLs ('/related_data/related_data_id/').
However, I worry that, let's say there are 5 related resources to the one I'm retrieving, is that I would perform 5 GET requests. I am writing an iPhone client and I'm wondering if this is how to properly do it using REST (that I'm returning URLs). A sample JSON response is this:
{
"meta": {
"limit": 20,
"next": null,
"offset": 0,
"previous": null,
"total_count": 2
},
"objects": [
{
"away_team": "/api/team/3/",
"country": "/api/country/1/",
"event_date": "2011-08-16",
"event_time": "06:00:00",
"event_timezone": "GMT",
"home_team": "/api/team/4/",
"id": "1",
"level": "/api/level/4/",
"resource_uri": "/api/event/1/",
"tournament": "/api/tournament/1/"
},
{
"away_team": "/api/team/4/",
"country": "/api/country/1/",
"event_date": "2011-09-29",
"event_time": "12:00:00",
"event_timezone": "UTC",
"home_team": "/api/team/3/",
"id": "2",
"level": "/api/level/1/",
"resource_uri": "/api/event/2/",
"tournament": "/api/tournament/6/"
}
]
}
Is this a proper way of doing it in REST, considering that "each URI must map to a resource" and all those things?
I am using Django and django-tastypie
Thanks in advance!
Yes; that is proper if the related resources are updated independently. REST architectures depend on caching for performance, and therefore work best with resources which act as atomic entities (see more here). That way, you can update resource B and have its representation be fresh without having to update resource A. See this SO comment for more design details.