Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

REST API Architecture: How to Represent Joined Tables

Question

I have a complex query that joins three tables and returns a set of rows with each row having data from it's sibling tables. How is it possible to represent this in a RESTful way?

FWIW I know there is not necessarily a "right" way to do it, but I'm interested in learning about what might be the most extensible and durable solution for this situation.

Background

In the past I've represented single tables that more or less mirror the literal structure of the url. For example, the url GET /agents/1/policies would result in a query like select * from policies where agent_id = 1.

Assumption

It seems like the url doesn't necessarily have to be so tightly coupled to the structure of the database layer. For example, if the complex query was something like:

select
  agent.name as agent_name,
  policy.status as policy_status,
  vehicle.year as vehicle_year
from
  policies as policy
  join agents as agent on policy.agent_id = agent.id
  join vehicles as vehicle on vehicle.policy_id = policy.id
where 1=1
  and policy.status = 'active';

# outputs something like:
{ "agent_name": "steve", "policy_status": "single", "vehicle_year": "1999" }

I could represent this QUERY as a url instead of TABLES as url. The url for this could be /vehicles, and if someone were to want to query it (with id or some other parameter like /vehicles?vehicle_color=red) I could just pass that value into a prepared statement.

Bonus Questions

  • Is this an antipattern?
  • Should my queries always be run against EXISTING tables instead of prepared statements?

Thanks for your help!

like image 526
Mike Fleming Avatar asked Mar 19 '18 15:03

Mike Fleming


People also ask

How do you associate two tables together?

The join is done by the JOIN operator. In the FROM clause, the name of the first table ( product ) is followed by a JOIN keyword then by the name of the second table ( category ). This is then followed by the keyword ON and by the condition for joining the rows from the different tables.

What happens when two tables are joined?

(INNER) JOIN : Returns records that have matching values in both tables. LEFT (OUTER) JOIN : Returns all records from the left table, and the matched records from the right table. RIGHT (OUTER) JOIN : Returns all records from the right table, and the matched records from the left table.

What relationship should a model have with an API interface?

Link objects are used to express structural relationships in the API. So for example, the top-level collections, singleton resources and sub-collections (including actions) are all referenced using link objects. Object links are used to express semantic relationships from the application data model.


1 Answers

You want to step back from the database tables and queries and think about the basic resources. In your examples, clearly these are agent, customer vehicle and policy.

Resources vs Collections

One misstep I see in your examples, is that you don't separate collections from resources using plurals which can be useful when you are dealing with Searching and logistically, for your controller routes. In your example you have:

GET /agents/1/policies

Suppose instead, that this was GET /agent/1/policies.

Now you have a clear differentiation between location of an Idempotent resource: /agent/1, and finding/searching for a collection of agents: /agents.

Following this train of though, you start to disassociate enumerating relationships from each side of the relationship in your API, which is inherently redundant.

In your example, clearly policies are not specifically owned by an agent. A policy should be a resource that stands on its own, identifiable via some Idempotent url using whatever ID uniquely identifies that policy for the purpose of finding that policy ie. /policy/{Id}

Searching Collections

What this now does for you, is allow you to separate the finding of a policy through: /policies where returning only policies for a specific Agent is but one of a number of different ways you might access that collection.

So rather than having GET /agents/1/policies you would instead find the policies associated with an agent via: GET /policies?agent=1

The expected result of this would be a collection of resource identifiers for the matching policies:

{ "policies" : ["/policy/234234", "/policy/383282"] }

How do you then get the final result?

For a given policy, you would expect a complete return of associated information, as in your query, only without the limitations of the select clause. Since what you want is a filtered version, a way to handle that would be to include filter criteria.

GET /policy/234234?filter=agentName,policyStatus,vehicleYear

With that said, this approach has pitfalls, and I question the approach for a number of reasons. If you look at your original list of resources, each one can be considered an object. If you are building an object graph in the client, then the complete information for a policy would instead include resource locators for all the associated resources:

{ ... Policy data + "customer": "/customer/2834", "vehicle": "/vehicle/88328", "agent": "/agent/32" }

It is the job of the client to access the data for an agent, a vehicle and a customer, and not your job to regurgitate all that data redundantly anytime you need some view of that data.

This is better because it is both restful, and supports many of the aims of REST to support Idempotency, caching etc.

This also better allows the client to cache locally the data for an Agent, and to determine whether or not it needs to get that data or just access data it already cached. At worst case there are maybe 3 or 4 REST calls that need to be made.

Bonus questions

REST has some grey area. You have to interpret Fielding and for that reason, there are frequently different opinions in regards to how to do things. While the approach of providing an api like GET /agents/1/policies to provide the list of policies associated with an agent is frequently used, there is a point where that becomes limiting and redundant in my experience, as it requires the end users to become familiar with the way you model relationships to the underlying resources.

As for your question on queries, it makes no difference how you access the underlying data and organize it, so long as you are consistent. What often happens (for the purposes of performance) is that the api doesn't return resource identifiers and starts returning the data as I illustrated previously. This is a slippery slope where you are just turning your REST api into a frontend to a bunch of queries, and at that point your API might as well just be: GET \query?filter=agent.name, policy.status, vehicle.year&from=policies&join=agents,vehicles&where=...

like image 172
gview Avatar answered Nov 23 '22 23:11

gview