Contract first vs code first: history is repeating itself

With the increased popularity of Graphql, so does the popularity of the frameworks such as (for python) graphene and Ariadne.

They are two frameworks able to reach the same target: a working GraphQL backend service catering for Graphql queries, mutation, and subscriptions.

However, they are taking opposite routes.

Graphene is taking code first route. So developers would write the python to tell the services the information it can server (resolve or mutate) and how to serve them. Something like:

import graphene
# Define your domain object, you can couple it with Django if they are from DB
class Company(DjangoObjectType):
    class Meta:
        model = dm.Company
....
    def resolve_staffs(self, info, **kwargs):
        return self.staffs.all()
## Define your resolvers
class CompanyQuery(graphene.ObjectType):
    companies= graphene.List(graphene.NonNull(Company), required=True)
    company = graphene.Field(Company)

    def resolve_company(self, info, **kwargs):
....
        return dm.User.objects.get(user_id=user_id)

    def resolve_companies(self, info, access_group_id=None):
        model = dm.Company.objects

....
        return model.filter(....)
## include into the Query
class Query(
    CompanyQuery,
....
):
    pass
## include different types
schema = graphene.Schema(query=Query, ....)

It starts with the code, to define the domain object and then the service how to resolve that instance, then will generate the contract people can reach:

For Ariadne, it will start the other way around, so we will first define the schemas:

## define the domain object
type CompanyInfo {
    name: String!
    address: [String!]!
    staff: [Satff!]!
    owner: String!
....
}
## define the resolver
type Query {
    companies: [CompanyInfo!]!
....
}
## Then we load the schema
schema = load_schema_from_path("...graphql")
## Then link the schema with the resolver
make_executable_schema(
    schema, company_resolver()....
)
## define the resolver
company = ObjectType("CompanyInfo")

@company.field("address")
def resolve_address(
    _: None, info: GraphQLResolveInfo, ....
) -> FrameworkInstance:
    return get_address(....)

Both routes will reach the same destination:

Now come with the debate: some prefer the Ariadne approach, claiming it’s for better decoupling.

However, looking back at history, this is the same debates happened many times:

We have this debate with web services, between contract first vs contract last, WSDL to code vs Code to WSDL.

We have this debate with ORM, whether domain first or last.

But what has happened? Both approaches survived through time.

Actually similar to DI, a lot of developers are aware of the decoupling writing the components through separate configuration files (XML). However, tight coupling wins out, we are almost using annotation everywhere.

The claim of schema first or contract first, so that it could be decoupled and have a single source of truth, actually worked out with code first as well. Look at what happens at REST services. We start with code first, write the services, then leave out to swagger to expose the single source of truth (the contract).

Ultimately, I think it boils to the maturity of the tooling. If either side has supreme tooling over the other side, it will win out regardless of concepts.

 

 

View at Medium.com

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s