Skip to content

Composite REST Resource

In REST-based web services, a composite resource refers to a resource that is a combination of multiple other resources. This is useful when you need to aggregate data from different sources into a single cohesive response.

  • A composite resource allows you to present a unified interface to the client while fetching and combining data from various data sources and remote APIs behind the scenes.
  • Composite resources are essentially a way to aggregate and present data from multiple endpoints in a cohesive manner.

Imagine an online travel booking service that provides information about flights, hotels, and car rentals. Each of these services could be managed by different APIs:

  1. Flights API: Provides information about available flights, airlines, schedules, etc.
  2. Hotels API: Provides information about hotel availability, room types, prices, etc.
  3. Car Rentals API: Provides information about car rental options, prices, car types, etc.

A composite resource could be created to offer a unified search feature, allowing users to find flights, hotels, and car rentals in a single query. Here’s how it might work:


Data Aggregation: Typical Workflow Example

Section titled “Data Aggregation: Typical Workflow Example”
  1. Composite Resource Endpoint: /api/v1/travel-options

  2. Client Request:

    • The client sends a request to the composite resource endpoint with parameters like destination, check-in date, check-out date, and number of travelers.
  3. Backend Processing:

    • The server-side logic calls the Flights API to get flight options to the specified destination.
    • It then calls the Hotels API to check available accommodations for the specified dates.
    • Finally, it queries the Car Rentals API to find available vehicles.
  4. Aggregation:

    • The responses from the individual APIs are aggregated into a single, cohesive response. This may involve data transformation, filtering, or merging.
  5. Unified Response:

    • The aggregated data is returned to the client as a single JSON response, which could look something like this:
    JSON representation example of a composite resource:
    {
    "flights": [
    {
    "flight_id": "123",
    "airline": "Airline A",
    "price": 300,
    "departure": "2024-09-01T10:00:00Z",
    "arrival": "2024-09-01T14:00:00Z"
    },
    {
    "flight_id": "456",
    "airline": "Airline B",
    "price": 350,
    "departure": "2024-09-01T12:00:00Z",
    "arrival": "2024-09-01T16:00:00Z"
    }
    ],
    "hotels": [
    {
    "hotel_id": "789",
    "name": "Hotel A",
    "price_per_night": 120,
    "rating": 4.5
    },
    {
    "hotel_id": "101",
    "name": "Hotel B",
    "price_per_night": 150,
    "rating": 4.0
    }
    ],
    "car_rentals": [
    {
    "car_id": "111",
    "company": "CarRentals A",
    "price_per_day": 50,
    "type": "SUV"
    },
    {
    "car_id": "222",
    "company": "CarRentals B",
    "price_per_day": 45,
    "type": "Sedan"
    }
    ]
    }
    json

Example of a composite resource implementation using Slim Framework:
/**
* Handles the HTTP GET request to retrieve the list of players and aggregate them with external data.
*
* This function fetches a list of players from the database based on the filters passed in the request's query parameters,
* and then fetches additional data from an external API (SportsDB) to aggregate both datasets into one.
*
* The aggregated dataset is returned as a JSON response.
*
* @param Request $request The HTTP request object, containing query parameters for filtering players.
* @param Response $response The HTTP response object that will be populated with the aggregated data.
* @param array $uri_args The URI arguments passed with the request, though not used in this method.
*
* @return Response The HTTP response object containing the aggregated data in JSON format.
*/
public function handleGetPlayers(Request $request, Response $response, array $uri_args): Response
{
$filters = $request->getQueryParams();
// Step 1) Pull the list of players from the database
$players = $this->players_model->getAllPlayers($filters);
// Step 2) Fetch a list of all teams in a league belonging to the specified country and/or sport.
$api_data = $this->getListOfLeagues();
// Step 3) Aggregate both datasets into a single dataset to be included in the response.
// Step 3.a) TODO: validate the $api_data before merging it with the players dataset.
$players["REPLACE_ME"] = $api_data;
// NOTE: the REPLACE_ME key needs to be changed.
// You should use a descriptive, meaningful key (e.g., leagues,
// exercises, shows, animals, planets, etc.)
return $this->renderJson($response, $players);
}
/**
* Fetches a list of leagues from the SportsDB API.
*
* This function uses the Guzzle HTTP client to make a GET request to the SportsDB API to retrieve a list of soccer leagues
* in a specified country. The API data is returned in a format that can be aggregated with other datasets.
*
* @return mixed Returns the data fetched from the SportsDB API, typically an array or object containing the list of leagues.
*/
public function getListOfLeagues() : mixed
{
/*
Fetch a list of leagues from the SportsDB API
Endpoint:
https://www.thesportsdb.com/api/v1/json/3/search_all_leagues.php?c=England&s=Soccer
Use Guzzle HTTP client to fetch data from the remote
API that you've selected.
* NOTE: Refer to Lab #5, Step #3).
*/
return [];
}
php

The following example shows what the composite resource response would look like after aggregating player data from the database with league data from the SportsDB API:

Example response from GET /players endpoint
{
"players": { // 👈 Your collection resource's data (e.g., players)
"meta": {
"current_page": 1,
"page_size": 10,
"total_pages": 5,
"total_records": 47
},
"data": [
{
"player_id": "1",
"name": "John Doe",
"position": "Forward",
"team": "Manchester United",
"jersey_number": 10
},
{
"player_id": "2",
"name": "Jane Smith",
"position": "Midfielder",
"team": "Chelsea FC",
"jersey_number": 8
},
{
"player_id": "3",
"name": "Mike Johnson",
"position": "Defender",
"team": "Arsenal FC",
"jersey_number": 5
}
]
},
"leagues": [ // 👈 Resource data from the external API (e.g., leagues)
{
"idLeague": "4328",
"strLeague": "English Premier League",
"strSport": "Soccer",
"strLeagueAlternate": "Premier League"
},
{
"idLeague": "4329",
"strLeague": "English League Championship",
"strSport": "Soccer",
"strLeagueAlternate": "The Championship"
},
{
"idLeague": "4330",
"strLeague": "Scottish Premier League",
"strSport": "Soccer",
"strLeagueAlternate": "SPL"
}
]
}
json