Graphqlconf
Graphqlconf
GraphQL with
Declarative
Configuration
Sai Ekbote
● Declarative configuration means saying what you want.
●
● not how to do it.
●
● Kubernetes YAML Manifests
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
template:
spec:
containers:
- name: my-app-container
image: my-app-image:1.0
resources:
limits:
cpu: "0.5"
memory: "512Mi"
● Terraform Infrastructure-as-Code (IAC)
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
● Docker Compose
version: '3'
services:
web:
image: nginx:latest
ports:
- "80:80"
app:
image: my-app:latest
ports:
- "8080:8080"
● 🍕 Ordering a Pizza
Advantages of Declarative Configuration
type User {
id: ID!
name: String!
email: String!
}
type Query {
user(id: ID!): User
users: [User]
}
return transformedRatings;
} catch (error) {
console.error('Error fetching ratings:',
error);
throw error;
}
const resolvers = {
Query: {
ratings: async (_, args) => {
try {
// Extract the API key from the GraphQL
arguments
const { apiKey } import
= args;
graphql.kickstart.tools.GraphQLQueryResolver;
// Define the headers for the HTTP request
import module Types const headers = {public class QueryResolver implements
class QueryType Authorization: `Bearer ${apiKey}`,
graphql.kickstart.tools.GraphQLQueryResolver;
< Types::BaseObject
GraphQLQueryResolver {
// You can add other
field :get_user, UserType, null: headers
true as needed
public User getUser(int id) {
public class
do }; implements
QueryResolver // Your logic to fetch user data by id
GraphQLQueryResolver
description
{ "Get a user by ID" return getUserById(id);
// Perform a GET request to
public Userargument
getUser(int
:id, id)
Int, { required: }true
localhost:8080/ratings with headers
// Your
end logic toconst
fetch user data
response by id
= await
return getUserById(id); // Example method
axios.get('http://localhost:8080/ratings', { to fetch user data
} def get_user(id:)
headers, private User getUserById(int id) {
# Your logic
}); toimport
fetch graphene
user data by // Implement your data retrieval logic
// Example
id
private User
method to fetch user data
// Assuming
get_user_by_id(id)
getUserById(int
ratings
// Implement
end
the
classid)
here
{ response data is an array
Query(graphene.ObjectType):
your data retrieval
get_user logic
// Return a User
= graphene.Field(UserType,
found
</
of object or null if not Resolvers>
here end const ratings = response.data;
id=graphene.Int())}
//
endReturn a User object or null} if not
// Transform the response data (e.g., multiply
found
each rating value def
by 2)resolve_get_user(self, info, id):
} # Example methodconst
to fetch user data logic
# Your
transformedRatings to fetch user data by id
= ratings.map((rating)
} def get_user_by_id(id)
=> ({ return get_user_by_id(id)
# Implement your data retrieval logic
...rating,
here # Example
value: function
rating.value * 2, to fetch user data
# Return a User object
})); or nil if not
def get_user_by_id(id):
found # Implement your data retrieval logic here
end return transformedRatings;
# Return a User object or None if not found
} catch (error) {
console.error('Error fetching ratings:',
error);
throw error;
}
},
},
};
Imperative
Declarative
(Programmatic)
apiVersion: graphql.gloo.solo.io/v1beta1
const resolvers = {
Query: {
ratings: async (_, args) => {
try {
// Extract the API key from the GraphQL arguments
const { apiKey } = args;
kind: GraphQLResolverMap
// Define the headers for the HTTP request
const headers = { import spec:
Authorization: `Bearer ${apiKey}`,
graphql.kickstart.tools.GraphQLQueryResolver;
Query:
// You can add other headers as needed
};
public class QueryResolver implements
import module Types
// Perform GraphQLQueryResolver
a GET request {
to localhost:8080/ratings
graphql.kickstart.tools.GraphQLQueryResolver;
class QueryType < Types::BaseObject
with headers
field :get_user, UserType, null: true
const response = await
public User getUser(int id) {
// Your logic to fetch user data by id
GetPlans:
public class
do QueryResolver implements
axios.get('http://localhost:8080/ratings',
return{ getUserById(id);
GraphQLQueryResolver
description
public Userargument
{
getUser(int
"Get a user by ID"
headers,
:id, id)
});
}
Int,{ required: true restResolver:
// Your
end logic to fetch user data by id
// Assuming the response // Example method
of to fetch user data
request:
return getUserById(id); data is an array
ratings private User getUserById(int id) {
} def get_user(id:)
const ratings = response.data;// Implement your data retrieval logic
# Your logic to fetch user data by
here
// Example
id
private User
method to//fetch
get_user_by_id(id)
getUserById(int
rating
userthe
Transform data
value by 2) id) {
response data (e.g., multiply each
// Return a User object or null if not headers:
import graphene
found= ratings.map((rating) =>
// Implement
end yourconst transformedRatings
data retrieval logic
here end
({ }
class Query(graphene.ObjectType):
...rating, } :method: GET
//
endReturn a User object or null if not
get_user *= 2,
value: rating.value graphene.Field(UserType,
found
:path: /v1/ratings
})); id=graphene.Int())
} # Example method to fetch user data
} return transformedRatings;
def get_user_by_id(id) def
{ resolve_get_user(self, info, id):
# Implement }your
catchdata
(error)
retrieval logic
here
# Your
console.error('Error
throw error;
logicratings:',
fetching to fetcherror);
return get_user_by_id(id)
user data by id
destination:
# Return a User
} object or nil if not
found
end },
}, # Example function to fetch user data
def get_user_by_id(id):
host: plans.prod.svc.cluster.local
};
# Implement your data retrieval logic here
# Return a User object or None if not found resolverResultTransform:
jq: .data.[] | .value = .value * 2
The Evolution of GraphQL
Users
Payments </SOAP>
Pricing
Inventory
Employees
github.com/Urigo/graphql-mesh
GraphQL
● Data Source Aggregation: Aggregate data from multiple sources, including REST APIs,
databases into a single GraphQL schema. This simplifies the process of querying and
retrieving data from various services.
● Auto-Generated Resolvers: GraphQL Mesh automatically generates resolvers for your
schema, so you don't have to write custom resolver functions for each data source. This
reduces the amount of boilerplate code you need to create.
● Schema Stitching: It uses schema stitching techniques to combine schemas from
different services, providing a unified API surface for clients. You can also define custom
stitching logic to handle complex data relationships.
● Flexible Configuration: GraphQL Mesh is highly configurable, allowing you to customize
how data is fetched, transformed, and exposed in your GraphQL schema. You can use
plugins and presets to extend its functionality and adapt it to your specific needs.
GraphQL
sources:
- name: Stripe
handler:
openapi:
source:
https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json
endpoint: https://api.stripe.com
operationHeaders:
Authorization: 'Bearer {env.STRIPE_TOKEN}'
documents:
- get-customers.query.graphql
type Query {
anything(message: String): JSON
@rest (
endpoint: "https://httpbin.org/anything"
method: POST
headers: [
{name: "User-Agent", value: "StepZen"}
{name: "X-Api-Key", value: "12345"}
]
)
}
type Query {
myQuery(key1: String, key2: String): RootEntry Request with headers to an endpoint
@rest(
method: POST
endpoint:
"http://dummy.restapiexample.com/api/v1/create"
)
}
Users
Payments </SOAP>
Pricing
Inventory
Employees
● Gloo GraphQL bundles your GraphQL server with a network proxy, adding all the benefits that
you can get from an API Gateway along with your GraphQL server
apiVersion: graphql.gloo.solo.io/v1beta1
kind: GraphQLResolverMap
spec:
Query:
GetPlans:
restResolver:
request:
headers:
:method: GET
:path: /v1/ratings
destination:
host: plans.prod.svc.cluster.local
resolverResultTransform:
jq: .data.[] | .value = .value * 2
Proxy is a GraphQL Server Architecture
Users
Payments </SOAP>
CACHING
Proxy +
Proxy
GraphQL
Pricing
EXTERNAL AUTH
● GraphQL Mesh:
○ Auto-generate schemas from existing non-GraphQL services
○ Configuration includes declarative transforms on the schema
○ Configuration includes plugins such as Rate limit, CSRF, Query Depth Limiting
● StepZen by IBM:
○ Inline declarative configuration via directives
○ Connect your GraphQL schema to database backends, as well as REST and gRPC backends
○ CLI to automatically generate GraphQL Schemas from existing backensd
● Gloo GraphQL:
○ Auto-generate schemas from existing REST, gRPC, and GraphQL services (via introspection) via CLI
○ Declarative data transforms via jq filter
○ Exists as a part of Envoy network proxy – a common component to many API gateway and service mesh
solutions
Declarative GraphQL with Envoy
Users
Payments </SOAP>
Inventory
Proxy is a GraphQL Server Architecture
Users
Payments</SOAP> </SOAP>
Proxy +
GraphQL Pricing
Inventory
Proxy is a GraphQL Server Architecture
Users
Payments </SOAP>
CACHING
Proxy +
Proxy
GraphQL
Pricing
EXTERNAL AUTH
Upstream
• GraphQL
Service
Declarative GraphQL Configuration
apiVersion: graphql.gloo.solo.io/v1beta1
kind: GraphQLResolverMap
spec:
Query:
type Plan {
GetPlans:
id: ID
pricing: Int restResolver:
payments: [Int] request:
} headers:
:method: GET
type Query {
GetPlans(userId: ID): [Plan] :path: /v1/plans/{$args.user_id}
} destination:
host: plans.prod.svc.cluster.local
resolverResultTransform:
jq: |
.[] | to_entries | .[]
| .pricing = .value.pricing | .id = .key
| .payments = (.value.payments | map( tonumber))
| del(.value, .key)
What if the REST response doesn't align
with the GraphQL schema?
[{ [{
"A23CD_E": { "id": "A23CD_E"
"pricing": 234, "pricing": 234
"payments": [12, 13.45, "45", 52] "payments": [12, 13, 45, 52]
} }]
}]
jq filter
GraphQL
Schemas
Resolver
Map
Deployment
Access
Policies
Recap: Why GraphQL in Service Mesh Proxy
● Explore tools like Gloo GraphQL, GraphQL Mesh, and StepZen to harness the power of
declarative configuration.
● Embrace the advantages of declarative approaches for clearer, more maintainable APIs.