Attributes in the GraphQL Schema
Our requirements say that a shopper on the storefront must be able to read a product’s attributes, but the GraphQL Product type we designed earlier has no field for them:
type Product {
id: ID!
name: String!
description: String!
priceCents: Int!
quantity: Int!
}
Storing attributes in a document does not make them available through the API. We need to extend the API’s schema. In this section we only cover reading attributes. Letting merchants change attributes through mutations is a separate part of the API design.
A list of names and values
Adding a field such as glaze or scent for each attribute would repeat the problem we had with columns. Every merchant uses the same schema, and introducing a new attribute would require us to change it. Instead, we add one field that holds attributes as a list:
type Product {
id: ID!
name: String!
description: String!
priceCents: Int!
quantity: Int!
attributes: [Attribute!]!
}
type Attribute {
name: String!
value: String!
}
The schema says that every attribute has a name and a value. The merchant decides which attributes each product has. A product with no attributes returns an empty list.
For this version of Shopend, we expose attribute values as display text. Our code does not do any math with a mug’s capacity or a candle’s burn time. It stores the merchant’s description of those details and returns it. That is why value is a String. If we later want to compare numeric capacities or filter by burn time, we will have to make more decisions, such as what type each attribute has and what units it uses.
Here is a query to /shops/wickandwax/graphql for the cedar candle:
{
product(id: 9001) {
name
quantity
attributes {
name
value
}
}
}
And here is the response:
{
"data": {
"product": {
"name": "Cedar candle",
"quantity": 20,
"attributes": [
{ "name": "scent", "value": "cedar" },
{ "name": "burn time", "value": "40 hours" },
{ "name": "quantity", "value": "3 candles" }
]
}
}
}
The merchant’s quantity attribute is an item in the list. It does not conflict with the quantity field. That field holds the stock count.
In the product document, attributes are stored as an object. In this response, they come back as a list of names and values. The GraphQL schema describes what clients can request. It does not have to match the structure of the stored document.
What changes
Adding attributes to Product and introducing Attribute do not break existing queries. Clients that do not request the new field continue to receive the fields they selected.
There are two changes here. One is moving storage to a document database. If the data access layer still offers the operations and guarantees that the rest of the application relies on, that change can stay inside the data access layer.
The other is adding product attributes to the application. That changes the API contract and requires each layer, from the data access layer up to the API layer, to pass those attributes along. In our layered design, the data access layer reads the stored attributes, and the API layer turns them into the list of names and values that the GraphQL schema defines. The existing purchase rules remain the same: attributes do not change how we calculate prices, reduce stock, or record an order.