govule govule - 3 months ago 8
C# Question

RESTful API with related resources

I'm in the process of designing my first serious RESTful API, which will sit above a WCF service.

There are resources like; outlet, schedule and job. A schedule is always owned by an outlet, and a schedule will contain 0 or more jobs. A job does not have to be on a schedule.

I keep coming back to thinking that resources should be addressable in the same type of way resources are addressed on a file system. This would mean I'd have URI's like:

/outlets
/outlets/4/schedules
/outlets/4/schedules/1000/jobs
/outlets/4/schedules/1000/jobs/5123


Things start to get messy though when considering how to pull resources back under different situations though.

e.g. I want a job not on a schedule:

/outlets/4/jobs/85
(this now means we've got 2 ways to pull a job back that's on a schedule)

e.g. I want all schedules regardless of outlet:

/schedules
or
/outlets/ALL/schedules


There are also lots of other more complex requirements but I'm sure you get the gist.

File system's have a good, logical way of addressing resources. You can obviously create symbolic links and achieve something approximating what I describe but it'd be messy. It'll be even messier once things get even slightly more complex, such as adding the ability to get schedules by date:

/outlets/4/2016-08-29/schedules


And without using query string parameters I'm not even sure how I'd request back all jobs that are NOT on a schedule. The following feels wrong because
unscheduled
is not a resource:

/outlets/4/unscheduled/jobs


So, I'm coming to think that file system type addressing is only going to work for the simplest of services (our underlying system has hundreds of entity types, with some very complex relationships and a huge number of operations).

Having multiple ways of doing the same thing tends to lead to confusion and messy documentation and I want to avoid it. As a result I'm almost forced to opt for going with the lowest common denominator and choosing very simple address forms - like the 3rd one below:

/outlets/4/schedules/1000/jobs/5123
/outlets/4/jobs/5123
/jobs/5123


From these very simple address forms I would then need to expand using query string parameters to do anything more complex, e.g:

/jobs?scheduleId=1000
/jobs?outletId=4
/jobs?outletId=4&fromDate=2016-01-01&toDate=2016-01-31


This feels like it's going against the REST model though and query string parameters like this aren't predictable, so far from the "no docs needed" idea.

OK, so at the minute I'm almost on the side of the fence that is saying in order to get a clean, maintainable API I'm going to have to go with very simple resource addresses, use query string parameters extensively and have good documentation.

Anyway, this doesn't feel like the conclusion I should have arrived at. Where have I gone wrong?

Answer

Welcome to the world of REST API programming. These are the hard problems which we all face when trying to apply general principles to specific situations. There is no clear and easy answer to your questions, but here are a few additional tips that may be useful.

First, you're right that the file-system approach to addressing breaks down when you have complex relationships. You'll only want to establish that sort of addressing when there's a true hierarchy there.

For example, if all jobs were part of a single schedule, then it would make sense to look to schedules/{id}/jobs/{id} to get to a given job. If you think of it from a data-storage perspective, you could imagine there being an XML file for each schedule, and the jobs would just be elements within that file.

However, it sounds like in this particular case your data is more relational. From a data-storage perspective, you'd represent each job as a row in a database table, and establish some foreign key relationships to tie some jobs to some schedules. Your addressing scheme should reflect this by making /jobs a top-level endpoint, and using optional query string parameters to filter by schedule or outlet when it makes sense to do so.

So you're on the right track. One more thing you might want to consider is OData, which extends the basic REST principles with a standards-oriented way of representing things like filtering with query string parameters. The address syntax feels a little "out there", but it does a pretty good job of handling the situations where straight REST starts falling apart. And because it's more standardized, there are tools available to help with things like translating from a data layer into an OData endpoint, or generating client-side proxy helpers based on the metadata exposed by that endpoint.

This feels like it's going against the REST model though and query string parameters like this aren't predictable, so far from the "no docs needed" idea.

If you use OData, then its spec combines with the metadata produced by your tooling to become your documentation. For example, your metadata says that a job has a date property which represents a date. Then the OData spec provides a way to represent filter queries for a date value. From this information, consumers can reliably produce a filter query that will "just work" because you're using a framework server-side to do the hard parts. And if they don't feel like memorizing how OData URLs work, they can generate a client proxy in the language of their choice so they can generate the appropriate URL via their favorite syntax.