Trying to get going on breeze but encountering the worst kind of error, which is none at all. It appears the metadata that I am producing is not being accepted by breeze. I know currently there are some issues with the metadata, such as 'foreignKeyNamesOnServer' has incorrect values in it and a bunch of others. The metadata I am producing can be viewed here (too large):
http://pastebin.com/ycP4jXxn
var serviceName = 'http://www.dockyard.com:8080/rest';
var entityManager = new breeze.EntityManager({serviceName: serviceName});
var entityQuery = new breeze.EntityQuery();
var query = breeze.EntityQuery.from("application")
entityManager.executeQuery(query)
.then(function (data) {
console.log(data);
}, function (error) {
console.log(error);
});
So the behaviour I am seeing is no javascript errors related to metadata parsing, the metadata is returning ok with 200 OK. The hit to /rest/application is returning 200 OK with the following data.
[{"#id":1,"id":1,"name":"dsad","deploymentStrategies":null,"versions":null,"groups":null},{"#id":2,"id":2,"name":"sss","deploymentStrategies":null,"versions":null,"groups":null},{"#id":3,"id":3,"name":"fdsfs","deploymentStrategies":null,"versions":null,"groups":null},{"#id":4,"id":4,"name":"fdsa","deploymentStrategies":null,"versions":null,"groups":null},{"#id":5,"id":5,"name":"dasda","deploymentStrategies":null,"versions":null,"groups":null}]
Promise is calling the error callback with: cannot execute _executeQueryCore until metadataStore is populated
The contents of the metadata store:
{"namingConvention":{"name":"camelCase"},"localQueryComparisonOptions":{"name":"caseInsensitiveSQL","isCaseSensitive":false,"usesSql92CompliantStringComparison":true},"dataServices":[{"serviceName":"http://www.dockyard.com:8080/rest/","hasServerMetadata":true,"jsonResultsAdapter":"webApi_default","useJsonp":false}],"_resourceEntityTypeMap":{"platform":"Platform:#com.psidox.dockyard.controller.model.dockyard","application":"Application:#com.psidox.dockyard.controller.model.application","host":"Host:#com.psidox.dockyard.controller.model.host","groupdeploymentstrategy":"GroupDeploymentStrategy:#com.psidox.dockyard.controller.model.application","dockyard":"Dockyard:#com.psidox.dockyard.controller.model.dockyard","configurationentry":"ConfigurationEntry:#com.psidox.dockyard.controller.model","hoststrategy":"HostStrategy:#com.psidox.dockyard.controller.model.application","dockerimage":"DockerImage:#com.psidox.dockyard.controller.model.docker","version":"Version:#com.psidox.dockyard.controller.model.application","docker":"Docker:#com.psidox.dockyard.controller.model.docker","hostproviderconfig":"HostProviderConfig:#com.psidox.dockyard.controller.model.host","hostprovider":"HostProvider:#com.psidox.dockyard.controller.model.host","metadataimpl":"MetadataImpl:#com.psidox.dockyard.controller.model","deployment":"Deployment:#com.psidox.dockyard.controller.model.dockyard","hosttype":"HostType:#com.psidox.dockyard.controller.model.host","group":"Group:#com.psidox.dockyard.controller.model.application","groupimplementation":"GroupImplementation:#com.psidox.dockyard.controller.model.application","deploymentstrategy":"DeploymentStrategy:#com.psidox.dockyard.controller.model.dockyard","groupdeployment":"GroupDeployment:#com.psidox.dockyard.controller.model.application","metadata":"Metadata:#com.psidox.dockyard.controller.model"},"_structuralTypeMap":{},"_shortNameMap":{},"_ctorRegistry":{},"_incompleteTypeMap":{},"_incompleteComplexTypeMap":{},"_id":0,"_deferredTypes":{}}"
I am pretty sure this error is related to Metadata store not being populated correctly from my metadata. Just wondering why Breeze is not throwing any type of error when it is encountering invalid metadata?
Edit:
After debugging the parse metadata call it appears that Breeze Metadata Schema Documentation is out of date. At a quick glance this is what it looks like has changed:
Key name "structuralTypeMap" has changed to "structuralTypes".
"structuralTypeMap" use to be a object with the key as the EntityTypeName and value was the Entity definition. Now it appears that "structuralTypes" is an array with the Entity definitions.
Suggestions also there should possibly be an exception thrown if the metadata doesn't contain any structuralTypes? Currently it is failing silently which isn't very helpful for debugging.
I fear they you've jumped into the deep end of the pool before learning to swim. I admire your bravery but I'm not surprised that you're struggling to stay afloat. You're not following any of the easy paths we've set out for you. I assume that is because none of these paths are suitable to your situation.
On the bright side, you've reinforced by sense that we soon must make it easier for developers who get their data from a custom REST service.
Problem #1
The Query results do not identify the EntityType and you didn't mention that you wrote a custom JsonResultsAdapter to cope with that. Your question and your MetadataStore contents below suggest that you are using the out-of-the-box Web API adapter which wouldn't know what to do with the JSON query results.
Here is one item in the JSON payload from your query, reformatted for readability
{
"#id": 1,
"id": 1,
"name": "dsad",
"deploymentStrategies": null,
"versions": null,
"groups": null
}
There's nothing in there to indicate to which EntityType this data belongs. Just looking I have no idea what type this is. Breeze won't know either.
You'll need to learn about the "JsonResultsAdapter" which is how Breeze interprets JSON data arriving from the server and maps it into instances of EntityTypes.
The Ruby Sample has a custom JsonResultsAdapter. It depends upon the fact that the server is explicit about the type of each object it returns; see how every Rails view adds a $type node (for example, the sessions:index view). This is the approach to take if you can control what the server sends the client.
The Edmunds Sample has a custom JsonResultsAdapter that has to infer the type by examining the characteristics of the JSON data. It's kind of a forensic exercise that you only want to indulge if you have to do so.
Problem #2
The MetadataStore you serialized is empty of all type information. Here it is reformatted for legibility
{
"namingConvention": {
"name": "camelCase"
},
"localQueryComparisonOptions": {
"name": "caseInsensitiveSQL",
"isCaseSensitive": false,
"usesSql92CompliantStringComparison": true
},
"dataServices": [
{
"serviceName": "http:\/\/www.dockyard.com:8080\/rest\/",
"hasServerMetadata": true,
"jsonResultsAdapter": "webApi_default",
"useJsonp": false
}
],
"_resourceEntityTypeMap": {
"platform": "Platform:#com.psidox.dockyard.controller.model.dockyard",
"application": "Application:#com.psidox.dockyard.controller.model.application",
... a bunch more ...
},
"_structuralTypeMap": {
},
"_shortNameMap": {
},
... more emptiness ...
}
}
I'm not really surprised, having discovered problem #3.
Problem #3
Your raw metadata doesn't match a format that Breeze understands. It looks like you cobbled it together by hand. It sure doesn't look like anything I recognize. It doesn't match the CSDL format from Entity Framework. It doesn't match the "Breeze Metadata Format" that you'd see when you exported a MetadataStore.
It's in trouble almost immediately. Here is how you start the definition of your first type:
"structuralTypeMap": {
"Group:#com.psidox.dockyard.controller.model.application": {
"shortName": "Group",
"namespace": "com.psidox.dockyard.controller.model.application",
Here is how it should begin:
"structuralTypes": [
{
"shortName": "Group",
"namespace": "com.psidox.dockyard.controller.model.application",
I accept your point that the Breeze Metadata Schema Documentation is incorrect. We should fix that.
I'm sympathetic with your argument that Breeze should have thrown an exception. I can see why it didn't throw. It simply ignored all the nodes that it didn't understand. A lot of parsers do that, not that that is a sufficient excuse.
In this case, it ignored the "structuralTypeMap" node and everything it had to say about types. When the parser was done, it had learned nothing at all about the types. Breeze can't know how many types you'll specify but it could act suspicious if there are none.
I confess I personally never thought to use this metadata schema description as my guide. That would be just about the hardest possible way to write metadata.
I suggest that you look at the documentation topic "Metadata by hand".
In sum
Please examine a simple example first. Maybe Edmunds. Maybe Ruby.
Learn to write metadata by hand; it's not hard.
Learn about the JsonResultsAdapter
We do hope soon to offer specific guidance for the developer who has a "vanilla" REST data service.
Related
I have the following data structure that contains an array of sectionIds. They are stored in the order in which they were completed:
applicationProgress: ["sectionG", "sectionZ", "sectionA"]
I’d like to be able to do something like:
GET /application-progress - expected: sectionG, sectionZ, sectionA
GET /application-progress?filter[first]=true - expected: sectionG
GET /application-progress?filter[current]=true - expected: sectionA
GET /application-progress?filter[previous]=sectionZ - expected: sectionG
I appreciated the above URLs are incorrect, but I’m not sure how to name/structure them to get the expected data e.g. Are the resources here "sectionids"?
I'd like to adhere to the JSON:API specification.
UPDATE
I'm looking to adhere to JSON:API v1.0
In terms of resources I believe I have "Section" and "ProgressEntry". Each ProgressEntry will have a one-to-one relationship with a Section.
I'd like to be able to query within the collection e.g.
Get the first item in the collection:
GET /progress-entries?filter[first]
Returns:
{
"data": {
"type": "progress-entries",
"id": "progressL",
"attributes": {
"sectionId": "sectionG"
},
"relationships": {
"section": {
"links": {
"related": "http://example.com/sections/sectionG"
}
}
}
},
"included": [
{
"links": {
"self": "http://example.com/sections/sectionG"
},
"type": "sections",
"id": "sectionG",
"attributes": {
"id": "sectionG",
"title": "Some title"
}
}
]
}
Get the previous ProgressEntry given a relative ProgressEntry. So in the following example find a ProgressEntry whose sectionId attribute equals "sectionZ" and then get the previous entry (sectionG). I wasn't clear before that the filtering of this is based on the ProgressEntry's attributes:
GET /progress-entries?filter[attributes][sectionId]=sectionZ&filterAction=getPreviousEntry
Returns:
{
"data": {
"type": "progress-entries",
"id": "progressL",
"attributes": {
"sectionId": "sectionG"
},
"relationships": {
"section": {
"links": {
"related": "http://example.com/sections/sectionG"
}
}
}
},
"included": [
{
"links": {
"self": "http://example.com/sections/sectionG"
},
"type": "sections",
"id": "sectionG",
"attributes": {
"id": "sectionG",
"title": "Some title"
}
}
]
}
I started to comment on jelhan's reply though my answer was just to long for a reasonable comment on his objection, hence I include it here as it more or less provides a good introduction into the answer anyways.
A resource is identified by a unique identifier (URI). A URI is in general independent from any representation format else content-type negotiation would be useless. json-api is a media-type that defines the structure and semantics of representations exchanged for a specific resource. A media-type SHOULD NOT force any constraints on the URI structure of a resource as it is independent from it. One can't deduce the media-type to use based on a given URI even if the URI contains something like vnd.api+json as this might just be a Web page talking about json:api. A client may as well request application/hal+json instead of application/vnd.api+json on the same URI and receive the same state information just packaged in a different representation syntax, if the server supports both representation formats.
Profiles, as mentioned by jelhan, are just extension mechanisms to the actual media-type that allow a general media-type to specialize through adding further constraints, conventions or extensions. Such profiles use URIs similar to XML namespaces, and those URIs NEED NOT but SHOULD BE de-referencable to allow access to further documentation. There is no talk about the URI of the actual resource other than given by Web Linking that URIs may hint a client on the media-type to use, which I would not recommend as this requires a client to have certain knowledge about that hint.
As mentioned in my initial comments, URIs shouldn't convey semantics as link relations are there for!
Link-relations
By that, your outlined resource seems to be a collection of some further resources, sections by your domain language. While pagination as defined in json:api does not directly map here perfectly, unless you have so many sections that you want to split these into multiple pages, the same concept can be used using standardized link relations defined by IANA.
Here, at one point a server may provide you a link to the collection resource which may look like this:
{
"links": {
"self": "https://api.acme.org/section-queue",
"collection": "https://api.acme.org/app-progression",
...
},
...
}
Due to the collection link relation standardized by IANA you know that this resource may hold a collection of entries which upon invoking may return a json:api representation such as:
{
"links": {
"self": "https://api.acme.org/app-progression",
"first": "https://api.acme.org/app-progression/sectionG",
"last": "https://api/acme.org/app-progression/sectionA",
"current": "https://api.acme.org/app-progression",
"up": "https://api.acme.org/section-queue",
"https://api/acme.org/rel/section": "https://api.acme.org/app-progression/sectionG",
"https://api/acme.org/rel/section": "https://api.acme.org/app-progression/sectionZ",
"https://api/acme.org/rel/section": "https://api.acme.org/app-progression/sectionA",
...
},
...
}
where you have further links to go up or down the hierarchy or select the first or last section that finished. Note the last 3 sample URIs that leverages the extension relation types mechanism defined by RFC 5988 (Web Linking).
On drilling down the hierarchy further you might find links such as
{
"links": {
"self": "https://api.acme.org/app-progression/sectionZ",
"first": "https://api.acme.org/app-progression/sectionG",
"prev": "https://api.acme.org/app-progression/sectionG",
"next": "https://api.acme.org/app-progression/sectionA",
"last": "https://api.acme.org/app-progression/sectionA",
"current": "https://api.acme.org/app-progression/sectionA",
"up": "https://api.acme.org/app-progression",
...
},
...
}
This example should just showcase how a server is providing you with all the options a client may need to progress through its task. It will simply follow the links it is interested in. Based on the link relation names provided a client can make informed choices on whether the provided link is of interest or not. If it i.e. knows that a resource is a collection it might to traverse through all the elements and processes them one by one (or by multiple threads concurrently).
This approach is quite common on the Internet and allows the server to easily change its URI scheme over time as clients will only act upon the link relation name and only invoke the URI without attempting to deduce any logic from it. This technique is also easily usable for other media-types such as application/hal+json or the like and allows each of the respective resources to be cached and reused by default, which might take away load from your server, depending on the amount of queries it has to deal with.
Note that no word on the actual content of that section was yet said. It might be a complex summary of things typical to sections or it might just be a word. We could classify it and give it a name, as such even a simple word is a valid target for a resource. Further, as Jim Webber mentioned, your resources you expose via HTTP (REST) and your domain model are not identical and usually do not map one-to-one.
Filtering
json:api allows to group parameters together semantically by defining a customized x-www-form-urlencoded parsing. If content-type negotiation is used to agree on json:api as representation format, the likelihood of interoperability issues is rather low, though if such a representation is sent unsolicitedly parsing of such query parameters might fail.
It is important to mention that in a REST architecture clients should only use links provided by the server and not generate one on their own. A client usually is not interested in the URI but in the content of the URI, hence the server needs to know how to act upon the URI.
The outlined suggestions can be used but also URIs of the form
.../application-progress?filter=first
.../application-progress?filter=current
.../application-progress?filter=previous&on=sectionZ
can be used instead. This approach should in addition to that also work on almost all clients without the need to change their url-encoded parsing mechanism. In addition to that he management overhead to return URIs for other media-types generated may be minimized as well. Note that each of the URIs in the example above represent their own resource and a cache will store responses to such resources based on the URI used to retrieve such results. Queries like .../application-progress?filter=next&on=sectionG and .../application-progress?filter=previous&on=sectionA which retrieve basically the same representations are two distinctive resources which will be processed two times by your API as the response of the first query can't be reused as the cache key (URI) is different. According to Fielding caching is one of the few constraints REST has which has to be respected otherwise you are violating it.
How you design such URIs is completely up to you here. The important thing is, how you teach a client when to invoke such URIs and when it should not. Here, again, link-relations can and should be used.
Summary
In summary, which approach you prefer is up to you as well as which URI style you choose. Clients, especially in a REST environment, do not care about the structure of the URI. They operate on link-relations and use the URI just for invoking it to progress on with their task. As such, a server API should help a client by teaching it what it needs to know like in a text-based computer game in the 70/80's as mentioned by Jim Webber. It is helpful to think of the interaction model to design as affordances and state machine as explained by Asbjørn Ulsberg .
While you could apply filtering on grouped parameters provided by json:api such links may only be usable within the `json:api´ representation. If you copy & paste such a link to a browser or to some other channel, it might not be processable by that client. Therefore this would not be my first choice, TBH. Whether or not you design sections to be their own resource or just properties you want to retrieve is your choice here as well. We don't know really what sections are in your domain model, IMO it sounds like a valid resource though that may or may not have further properties.
I'm in a situation where I have something similar to shopping cart. I have a set of products with known IDs I then want to create a subset with modified prices and later modify this subset
Superset
[{
"productId":1,
"price":1.99
},
{
"productId":2,
"price":2.99
},
{
"productId":3,
"price":3.99
},
{
"productId":4,
"price":4.99
}]
...
Modified Subset
[{
"productId":1,
"price":1.59
},
{
"productId":3,
"price":2.59
}]
Then I want to modify the subset again to look like
[{
"productId":1,
"price":1.79
},
{
"productId":2,
"price":3.59
}]
All I can think of is client sending a POST request like
{
"productsAdded":[
{
"productId":2,
"price":3.59
}
],
"productsModified":[
{
"productId":1,
"price":1.79
}
],
"productsDeleted":[
{
"productId":3,
"price":2.59
}
]
}
The constraints are that I would like to avoid multiple calls to correct verbs and not send the entire subset. As the actual objects have many more fields and there are thousands of objects in a subset. The update then saves the state triggers a long running fire and forget task.
The problem I see with this is that client potentially has to create a delta of
the state and the server has to reconstruct the state from the message.
This might not be a bad approach, but I am wondering if there are alternative solutions
Updating a collection the RESTful way
The Atom Publishing Protocol is built around the idea of modifying collections of atom entries in the REST architectural style. You might start out by reviewing that design.
All I can think of is client sending a POST request like
That's really close to a JSON Patch representation.
The idea is that if the client and the server share the same (usually standardized) understanding of the media-type, then they can continue to collaborate even as they are developed independently.
So using POST to pass a JSONPatch representation of the change to the resource is a good starting point; if you don't like JSONPatch (perhaps because it is domain agnostic), you could define your own more specific media type and use that.
(Note: though the message type is called "patch", you don't actually need to use the PATCH method in your API. POST on its own, PATCH on its own, or both are all acceptable alternatives.)
Your proposed solution has all of the caveats you mentioned and more. Suggestions:
Modify each object individually through independent requests using
the appropriate HTTP verbs POST, PUT, PATCH, DELETE.
Return the entire collection to be processed on the server using the
appropriate HTTP verb PUT.
Return the partial collection to be processed on the server using the appropriate HTTP verb PATCH.
Additional reference links:
https://restfulapi.net/http-methods/
http://www.restapitutorial.com/lessons/httpmethods.html
https://www.rfc-editor.org/rfc/rfc7231#section-4.3.3
https://www.rfc-editor.org/rfc/rfc5789
http://roy.gbiv.com/untangled/2009/it-is-okay-to-use-post
I could not find a definitive document mentioning if UI rendering hints can be sent back as REST metadata or not. First of all, what all can be classified as REST metadata? Surely, data type of attribute can be metadata but can presentation hints like whether an attribute is single-valued or multi-valued also be described metadata? What about "ishidden" and "isReadOnly"? As per my understanding information like min/max/regexp/fixed value are OK as metadata but not sure if anything related to presentation hints like the one I mentioned above are good candidates for REST metadata? Any pointers would be of utmost help.
Thanks,
Paddy
Honnestly you are free to send back to the REST client what you want in your payload. However I'm not sure that it's a good idea to always use these UI-oriented metadata. As a matter of fact, you can also have applications that consume data without having UI issues.
You could implement a mechanism that let you select the metadata level you want to return to the REST client within the content negociation (Conneg based on the header Accept) in a similar way that what OData does. Here is a sample below:
GET serviceRoot/People
Accept: application/json;odata.metadata=minimal
You could imagine the following values for the header Accept:
No metadata: application/json;metadata=none
Structural metadata (property type, ...): application/json;metadata=minimal
Validation metadata (useful to determine the expected values for properties): application/json;metadata=validation
UI rendering metadata (readonly, ...): application/json;metadata=rendering
You can then structure the content as described below:
{
"property1": "value",
// Structural
"property1#metadata": {
"type": "string"
},
"property2": 10,
// Structural + validation
"property2#metadata": {
"type": "integer"
"minValue": 2,
"maxValue": 15
},
"property3": 10,
// Structural + ui rendering
"property3#metadata": {
"type": "integer"
"minValue": 2,
"maxValue": 15,
"readOnly": true,
"hidden": false
}
}
If you want to have a look at how metadata are handled within OData v4, you can use the following links from odata.org:
Basic tutorial - http://www.odata.org/getting-started/basic-tutorial/
Advanced tutorial - http://www.odata.org/getting-started/advanced-tutorial/
EDIT: in a comment, inf3rno underlines the header Prefer could also be used to describe the meta level required.
Here is a sample of use below:
GET serviceRoot/People
Accept: application/json
Prefer: metadata=rendering
Hope it helps you,
Thierry
I was wondering if there were any features hidden in Angular or exposed by some 3rd-party libraries to easily create HATEOAS-compliant Restful clients.
On backend side, I am using Spring Data/REST to produce an HATEOAS JSON API.
Consuming it, though, is quite another story.
For instance, I've got those 3 entities:
Company {name, address}
Employee {firstName, lastName, employer[Company]}
Activity {rate, day, employee[Employee], client[Company]}
and requesting an activity (the most complex entity of the model) produces something like this:
{
links: [],
content: [{
rate: 456,
day: 1366754400000,
links: [{
rel: "self",
href: "http://localhost:8080/api/activities/1"
},
{
rel: "activities.activity.client",
href: "http://localhost:8080/api/activities/1/client"
},
{
rel: "activities.activity.employee",
href: "http://localhost:8080/api/activities/1/employee"
}]
}]
}
My API talks in terms of REST (resources identified by links).
An Activity has an Employee for instance. What I really want to use is : {rate: 456, day: 1366754400000, employee: {firstName:"xxx", lastName:"xxx" ...}}.
However, as you can see in the first output, my Activity only contains a link to the employee, not its data. Is there anything in Angular or in a 3rd-party library to resolve those links and embed the resulting data instead?
Any input on this?
Thanks in advance!
Checkout angular-hateoas. ITs an AngularJS module for using $resource with a HATEOAS-enabled REST API.
You could write a Response Transformation that would inspect your returned object, check for links, and resolve them before returning the response. See the section "Transforming Requests and Responses" in the $http service documentation.
Something like this:
transformResponse: function(rawData) {
var json = JSON.parse( rawData );
forEach( json.content.links, function(link) {
// resolve link...
});
return json;
}
Since the "resolve link" step is itself an $http call, sub-references would also be resolved. HOWEVER, since these are asynchronous, you would likely return a promise instead of the real value; I don't know if the transform function is allowed to do this.
As #charlietfl pointed out, however, please note that this will result in several HTTP calls to return a single entity. Even though I like the concept of HATEOAS, this will likely result in sluggishness if too many calls are made. I'd suggest that your server return the data, or some of it, directly, PLUS the link for details.
Based on your comment about wanting to work with data as against links on the client, I think Restangular would be a good fit.
I've been using angular-hal for one of my projects. It was a Spring HATEOAS backend. And I didn't run into any issues. It handles parametrized resources. I suspect it only supports HAL so since you're using Spring Data Rest you probably have to configure it to generate HAL compliant responses.
I think the confusion may be that you are asking for an Angular solution, when what you really want to do is have Spring Data send a more complete JSON response. I.e, you really just want the server to return the employee data as part of the response JSON, rather than having the client perform extra steps to look it up. I don't know what data store you are using in Spring Data, but the general solution would be to add public getEmployee() method to your Activity class and then annotate the method with #RelatedTo and #Fetch (this would be the setup for Neo4J -- may be different annotations for your flavor of Spring Data). This should cause Spring HATEOAS to include the Employee record within the response for /activity. Hope this helps.
How to I represent a complex resource for a REST post?
Hello,
Currently I have an application which when the user hits "save" it iterates over all of the form elements and creates one mass object which manages a:
var = params = [{
attributes1: form1.getValues(),
attributes2: form2.getValues(),
.. ..
}];
I then send this mass object via a RPC POST to my "Entity" model service.
This entity which I wish to persist data for is quite complex. All in all, the data is spread accross about 30 tables. To help explain my actual question, the "entity" is a building (as in a physical property/house/apartment).
What I would like, is to be able to turn my mess into a RESTful API for saving properties.
The problem I have is that, saving details for a single model that spans a single table is fine. How do I structure my data object for transport when the model has
many to many relationships
one to many relationships
one to one relationships
For example:
Here is a WATERED down version of what I might have on a property and the sample data
propertyId: 1,
locationId: 231234,
propertyName: "Brentwood",
kitchenFeatures: [
{ featureId: 1, details: "Induction hob"},
{ featureId:23, details: "900W microwave"}
],
propertyThemes: [ 12,32,54,65 ]
This actually goes on a lot more.. but you can get the general gist. kitchenFeatures would be an example of a many-to-many, where I have a featuresTable which has all of the features like so:
`featureId`, `feature`
1 "Oven Hob"
23 "Microwave"
and propertyThemes would be an example of another many-to-many.
How am I expected to form my "object" to my RESTful service? Is this even possible?
ie. If I want to save this property I would send it to:
http://example.com/api/property/1
The approach I would use here is hypermedia and links:
/property
/property/{id}
/property/{id}/features/{id}
Depending on your domain you might even get away with:
/property/{id}/features/{name}
or
/property/{id}/features/byname/{name}
Thus you can do REST operations and serve JSON or XHTML hypermedia.
Property details:
Request: GET /property/1
Response:
{
..
"name": "Brentwood",
"features": "/property/1/features"
..
}
Brentwood's features:
GET /property/1/features
{
..
"Kitchen": "/property/1/features/1",
"Dog Room": "/property/1/features/dog%20room",
..
}
GET /property/1/features/1
{
..
"Induction hob": "/property/1/features/1/1",
"900W microwave": "/property/1/features/1/23",
"nav-next" : "/property/1/features/dog%20room",
..
}
To add a relation you can do something like this:
POST /property/1/features
{
..
"Name": "Oven Hob"
..
}
If you know what the relation will be you use a PUT:
PUT /property/1/features/23
{
..
"Name": "Oven Hob"
..
}
You can serve multiple media types:
GET http://host/property/1/features/dog%20room.json
GET http://host/property/1/features/dog%20room.xhtml
For the response in xhtml the response can use named links like this:
..
Kitchen
..
There are other aspects of REST that you can use such as response code which I did not include above.
Thus, to model relations you make use of links which can be in itself a resource that can be operated on with GET, PUT, POST and DELETE or even custom verbs such as ASSOCIATE or LINK. But the first four are the ones that people are used to. Remember PUT is idempotent but not POST. See PUT vs POST in REST
Edit: You can group your links into JSON arrays to give structure to your hypermedia.
I think you're really asking, "How do I represent complex data in a form suitable for transmission within a POST?", right? It's less to do with REST and more to do with your choice of media type. I would suggest starting with a pure JSON representation, using arrays and cross-referenced ID fields to map the relationships. You could also do this with XML, of course.
The examples you gave look right on the money. You just need to ensure that both parties (browser and server) agree on the structure and interpretation of the media type you use.
I'm dealing with the exact same thing. I opted to not use id's anywhere, but use urls everywhere an id would normally be expected.
So in your case, the kitchenfeatures could simply be an array with urls to:
/feature/1
/feature/23
And the themes to
/propertyTheme/12
/propertyTheme/32
etc..
In the case of many-to-many relationships, we update all the relations as a whole. Usually we simply dump the existing data, and insert the new relationships.
For one to many relationships we sometimes extend the urls a bit where this makes sense. If you were to have comments functionality on a 'property', this could look like
/property/1/comment/5
But this really depends on the situation for us, for other cases we put it in the top-level namespace.
Is this helpful to you?