Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should an API consume itself or call the database directly for other resources?

This question is an extension although separate of this question posted earlier.

So we have our basic example of a garage has multiple cars so our endpoints are

/garages
/garages/{id}
/garages/{id}/cars
/garages/{id}/cars/{id}
/cars
/cars/{id}

We can get all cars from multiple garages with /cars?garage[id]=1,2,3

which is cool. But what I am wondering now is on the inside of the API.

There are two ways I can think of to do this:

Direct filtering in the /cars endpoint

This means that we in the query to get the cars we do some joins and add in some where's.

The benefit of this approach is that we will end up with the minimum amount of queries.
The con of this approach is that we end up maintaining the garages resource in two places. any time the garage gets a new property we must now support this in the cars endpoint too.

Calling the /garages endpoint from the /cars endpoint

This means we call the /garages endpoint from within the /cars endpoint with /garages returning the id of all cars from the matching garages. We then proceed to get the cars from back within the /cars endpoint.

The benefit of this method is that the resources are self contained.
The con of this approach is that we are going to end up with multiple calls to the database. Also passing around the authentication details could get cumbersome (Let's assume Oauth 2.0)

So what is the most appropriate way to do this? I am leaning towards the second method however I am concerned that this could become a real hassle if we want to do some more advanced queries.

like image 534
Hailwood Avatar asked Oct 21 '22 02:10

Hailwood


1 Answers

Use your own endpoints (This is know as service layering and has many benefits), but don't be tightly coupled to your endpoints.

Firstly, I wouldn't query by numeric ID as this is tightly bound to your implementation. If you use URL as you garage identifier, then you'll get far more flexibility and can easily add support for garages in other systems than your own.

Use hypermedia controls(i.e. links and forms) to add a search form on the cars list. Let's assume you cars list looks something like (scheme and host excluded for brevity)

<cars self="/cars">
    <car href="/cars/0"/>
    <car href="/cars/1"/>
    ...
</cars>

NOTE: you'll want to add some summary properties to the cars (like registration, make, model, etc to make the search meaningful, without have to return the full car entity).

to add a search, we could add something like

<cars self="/cars">
    <car href="/cars/0"/>
    <car href="/cars/1"/>
    ...
    <form name="search" action="/cars" method="get">
        <input name="garage" type="URL"/>
        <!-- other things to search for can go here -->
    </form>
</cars>

In your resource repository (e.g. database), if you cars store the URL of the garage they are in, then this query can be executed without needing query the garages at all and as mentioned above supports having garages in a completely different system, assuming the other system uses your media-type or you support their media-type.

Obviously you'll need access to the garage URLs when your searching, this can be done by have a garage search as well. e.g.

<garages self="/garages">
    <garage href="/garage/0"/>
    <garage href="/garage/1"/>
    ...
    <form name="search" action="/garages" method="get">
        <input name="paint" type="string"/>
        <!-- other things to search for can go here -->
    </form>
</garages>

So the flow would be, when a user wants to search for a car in a number of garages, they first search for those garages and add the ones they are interested in to a list (which is just a list of URLs). They then search for the cars using the garage list as an input.

In this scenario the garage and the cars are only coupled by the URLs.

You could extend this further by having a links to a cars search on the garages collection. For instance, just say we searched for garages with yellow paint, we might get a collection like

<garages self="/garages?paint=yellow">
    <garage href="/garage/24"/>
    <garage href="/garage/36"/>
    ...
</garages>

To get the list of cars for this collection, we could add a link as follows

<garages self="/garages?paint=yellow">
    <garage href="/garage/24"/>
    <garage href="/garage/36"/>
    <link rel="cars" href="/cars?garage=/garage/24,/garage/36"/>
</garages>

This would work for a small list or garages, but becomes problematic when the list of garages grow, in which case the URL for the cars will become too long.

Instead, we can use the URL of the search as an input parameter. e.g.,

<garages self="/garages?paint=yellow">
    <garage href="/garage/24"/>
    <garage href="/garage/36"/>
    <link rel="cars" href="/cars?garages=/garages?paint=yellow"/>
</garages>

In this case, when you follow the cars link, your service will need to execute the garages query, retrieve a list of cars for each garage and then return the merged list of cars. Besides the shorter URL, the benefit of this query is that it will always give you the list of cars at garages with yellow paint, event when the paint stock at the garages changes.

How does your service get the list of cars at a garage? The garage entity can have a link to a cars collection. e.g.

<garage self="/garage/24">
    ... details about the garage ...
    <link rel="cars" href="/cars?garage=/garages/24"/>
</garages>

So as I said at the top, use your own endpoints, but doing be tightly coupled to them by assuming they are your own endpoints. Construct your hypermedia controls for your entities and when your service needs to use them, treat them like any other external API.

Finally (and slightly off-topc), to support large collections of cars and garages, you can add pagination to your collections. e.g.

<garages self="/garages?page=2">
    <garage href="/garage/10"/>
    <garage href="/garage/11"/>
    ...
    <link rel="next" href="/garages?page=3"/>
    <link rel="prev" href="/garages"/>
</garages>

You might even want consider having your collections conform with RFC5005 Paged Feeds and Complete Feeds, so they can be consumed with standard tools.

UPDATE

Here is an example of a garages collection with hypermedia controls in JSON, using some of the concepts from above:

{
    "self": "/garages?paint=yellow&page=2",
    "garages": [
        {
            "href": "/garage/24"
            //... summary properties for this garage go here ...
            //... you can even add a "media-types" array, to tell the service consumer
            //    what media types the garage is available in... 
        },
        {
            "href": "/garage/36"
        }
    ],
    "next": {
        "href": "/garages?paint=yellow"
    },
    "prev": {
        "href": "/garages?paint=yellow&page=3"
    },
    "cars": {
        "href": "/cars?garages=/garages?paint=yellow"
    },
    "search": {
        "href": "/garages?paint=yellow",
        "method": "GET",
        "inputs": {
            ... form input parameters go here ...
        }
    }
}
like image 198
Tom Howard Avatar answered Oct 28 '22 00:10

Tom Howard