Summary
Tatari Engineering, which transitioned to GraphQL for API development in 2018, introduced a concept called DeprecatedField to safely rename fields in GraphQL while maintaining backward compatibility. This homegrown extension, built on top of python’s graphene GraphQL library, allows Tatari to incrementally deploy field name changes in the backend without breaking existing frontend code, providing flexibility and stability to their API.
Source code for DeprecatedField
can be located in this gist
Introduction to GraphQL Transition at Tatari
In 2018, Tatari made the decision to transition to GraphQL for our API development. This decision was driven by our desire to improve the efficiency and flexibility of our API development process, as well as to provide a better experience for our customers and stakeholders. Since then, our team has been using GraphQL to power our APIs and we have seen significant improvements in our development workflow and API performance.
Challenges of Renaming Fields in GraphQL
In a fast growing startup that is continually reacting to the market and business requirements, change is a constant.
An essential part of building and maintaining APIs is ensuring that the business domain model satisfies the requirements.
Renaming fields is an essential part of building APIs. It helps to ensure that the API is consistent with an evolving
business domain model and satisfies the requirements of the API consumers. However, renaming fields can have a significant
impact on the client code that consumes the API. It can break the existing code that relies on the old field names.
Therefore, we needed a way to rename fields without breaking the client code. There is no built-in mechanism to rename a
field in the GraphQL spec (although there is a spec to mark a field as deprecated). At Tatari, we introduced a concept
we call DeprecatedField
, which is a homegrown extension we have built on top of python’s graphene GraphQL library, which
enables us to rename fields safely in GraphQL with backwards compatibility. This gives us the flexibility to maintain
interoperability between the frontend and backend, and deliver these changes incrementally over time without having to
coordinate a large synchronous release across the organization.
A Product Use Case that Required an Engineering Solution: Extending the “Campaign” Concept in 2023
In 2023, we broadened the interpretation of the term “campaign” on our platform, now representing more than individual inventory items by including entire collections, along with an advertiser’s goals and objectives. The challenge emerged from the persistence of our prior definition of “campaign,” which clashed with the introduction of the new definition.
Introducing DeprecatedField Concept
Our API would have become inconsistent in our use of “campaign” unless we renamed several fields related to the old usage
of “campaign” across the entire GraphQL schema. The frontend code had several GraphQL queries that were referencing
“campaign” from several different objects in our GraphQL schema. Renaming field names in GraphQL is a tedious and
error-prone task that could easily break our frontend application without careful planning and coordination. However,
with DeprecatedField
, we were able to rename several fields and deploy those changes in isolation in our backend,
without having to make parallel changes on the frontend.
DeprecatedField
is a piece of software that is compatible with the graphene
library that allows us to rename fields
on ObjectType
and InputObjectType
safely. It provides a way to map an old field name to a new field name while
preserving the existing field’s type, arguments, and resolver. By using DeprecatedField
, we can ensure that the client code
that relies on the old field name continues to work as expected.
Usage of DeprecatedField in Practice
To use DeprecatedField
, we need to define a list of DeprecatedField
objects for each ObjectType
or InputObjectType
that we want to rename. Each DeprecatedField
object has two attributes: old_name
and new_name
. The old_name
attribute specifies the old field name, and the new_name
attribute specifies the new field name. We can define
the DeprecatedField
list as a class decorator on the ObjectType
or InputObjectType
class.
For example, suppose we have an ObjectType
called Campaign
that has two fields, id
and name
. You want to rename
the name
field to title
. Without deprecate_gql
, we would have had to do something like this:
class Campaign(graphene.ObjectType):
id = graphene.Int(required=True)
title = graphene.String()
name = graphene.String(deprecation_reason=’replaced by title’)
def resolve_title(self, info):
return self.title
def resolve_name(self, info): # Notice that we have a resolver that points to the new field name
return self.title
Here’s how you can define the Campaign
class using DeprecatedField
:
@deprecate_gql(
[
DeprecatedField(old_name='name', new_name='title'),
]
)
class Campaign(graphene.ObjectType):
id = graphene.Int(required=True)
title = graphene.String(required=True)
def resolve_title(self, info):
return self.title
In the example above, we defined the deprecate_gql
decorator as a list of DeprecatedField
objects. The DeprecatedField
object specifies that the old field name is name
, and the new field name is title
. When the client queries the
Campaign
object, they can use either name
or title
as the field name, and both will work as expected.
You can also use DeprecatedField
to rename fields on InputObjectType
. For example, suppose you have an InputObjectType
called CreateCampaignInput
that has two fields, name
and budget
. You want to rename the name
field to title
.
Here’s how you can define the CreateCampaignInput
class using DeprecatedField
:
@deprecate_gql(
[
DeprecatedField(old_name='name', new_name='title'),
]
)
class CreateCampaignInput(graphene.InputObjectType):
budget = graphene.Int()
title = graphene.String()
class CreateCampaignMutation(graphene.Mutation):
class Arguments:
input_data = CreateCampaignInput(required=True)
campaign = graphene.Field(Campaign)
def mutate(self, info, input_data):
# Extract data from input_data
budget = input_data.budget
title = input_data.title
# Create a new Campaign object with the extracted data
new_campaign = Campaign(budget=budget, title=title)
# Save the new Campaign object to the database
new_campaign.save()
# Return the new Campaign object
return CreateCampaignMutation(campaign=new_campaign)
Without DeprecatedField, we would have had to modify our mutate code to do something like this:
title = input_data.title or input_data.name
Benefits of Using DeprecatedField
Encapsulating Code Changes in a decorator
The DeprecatedField feature allows us to encapsulate field changes using a Python decorator. When we eventually need to
eliminate the old field name from the GraphQL schema, there’s no need to search for conditional expressions
(e.g., input_data.title or input_data.name
). We can just delete the decorator. The original developer responsible for
the field replacement can easily identify these references during the initial work. Later, when it’s time to remove the
field from the schema, another developer can effortlessly do so without the need to locate specific points in the
code—simply removing the decorator accomplishes this task.
Gradual Deployment and Flexibility
The DeprecatedField class allows Tatari developers to easily modify and customize the fields of our GraphQL schema. By using DeprecatedField, our backend developers can safely and confidently make these changes without affecting our existing front end applications. The front end engineers can gradually roll out the new field names at their own pace. This can lead to a more flexible and stable API that is easier to maintain and evolve over time.