Backend Developer's View on GraphQL
Mafinar Khan
Software Developer, Planswell
In this Talk...
- History - The Project
- Backend Workflow Patterns
- Code Organization Practices
Part I, History - the Project
Vehicle Tracking System
- Django + React
- Postgres
- CouchDB
- Redis
- Python (Twisted)
Features
- Realtime Monitoring
- Variety of Devices and/or Phone
- Analytics Reports
- Hierarchical Authorization System (Three Levels)
- Timeline
- Geofence
- Remote Locking/Engine Control
- Realtime Image and Video Streaming
Architecture at a Glance
- Different Devices come with Different Features
- Bi-directional Communication from web backend to TCP Server
- Vehicle Data stored in CouchDB
- Business Information stored in Postgres
- Message Queue for Image Transfers
- Redis to store most recent data and Geofence Violations
- Nearest location query for over 1 million Location Points
Somewhat happy with REST
Until GraphQL Came out...
GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system you define for your data.
So front-end works like that Postgres Console now?
Initial set of Reactions
- How can I fit my database there?
- So `groups/list` is now `groups { ... }`?
- Why mutation? Why not just use `groups { add() }`
- Django Models = GraphQL Schema?
- Where's a DRF equivalent?
- It doesn't look secured
Week 1: REST == GraphQL
Future us: Noooooooo!
Week 2: GraphQL Types == Django Models
GraphQL
- Is not REST queried different
- Is not a fancy collection of ActiveRecord records
- Queries and Mutations are separate paths
- Different way of thinking your data
- Much like database console, only-
- You design the schema
- You design the fetching logic
- You don't care about how the data comes
{
user {
id
vehicles {
id
geofence {
status
violationTime
}
history(from: "2018-01-01") {
locations {
id
}
}
lastLocation {
lat
lng
landmark
speed
}
deviceFeatures {
canSendImage
canStreamVideo
canControlEngine
}
}
}
}
}
User Information: Postgres
GeoFence: Redis
Vehicle Last Location: Redis
Photo: File System
History: CouchDB
mutation {
stopEngine(input: { id: "XX-XX-XXXX" }) {
vehiclePayload {
id
engineStatus
}
}
}
mutation {
takePhoto(input: { id: "XX-XX-XXXX" }) {
photoPayload {
data
timestamp
message
}
}
Sends "stop" signal to the GPS Server that holds device connections
Sends "take photo" signal to the GPS Server that holds device connections, waits for data to be loaded
Thinking in GraphQL
- The shape of the data you want is of a Graph
- IDs are important
- Queries look for data
- Mutations look for data, expecting modification
- Make sure to pack inputs
- Remember the ID
- Package output in a separate package
Part II, Backend Workflow Patterns
Quick Overview
Here's how it goes
- Assuming you have everything else ready-
- Post fullstack meeting: Come up with Schema
- Implement those Schemas
- Map schemas with data fetching logic
- Database query?
- Calling other REST API?
- Third party services?
- Authentication? Authorization?
- Take care of validation
- Pack them in the schema data structure
In other words
- (that one) URLĀ
- Schema Design (class? struct? record?)
- Serialization Technique
- Pre/Post checks (Middleware?)
- Resolver Function/Method
- Domain Logic (Plain old language code)
Be careful about automagic table to schema translators, your usecase and API is yours to design, team work FTW!
Tools! Tools!!
- You are blessed with GraphiQL, use it while you're in Dev mode
- Use middleware/dev mode only schemas to know about how the query/mutations impact the system
- Make middleware (something that intercepts resolver/serialization/dispatch) to know stuff in-transit
- Make sure to remove them all in production
Part III, Code Organization Practices
File Organization?
- Have a separate module for GraphQL
- Hook one (or two for GraphiQL) handler to URL(s)
- Divide it into two packages
- Something like `lib`, communicates with database, APIs, does computations etc
- Serializer module to translate native data-type to GraphQL schema types and vice versa
- Something like `web` package that includes type definitions, resolvers, queries and mutations
Tips
- `lib` functions may not know they will be used for GraphQL, they just fetch and mutate data
- `web` functions should only concern themselves with web related things and completely rely on `lib` functions to get the data and serializers to convert them
- Resolvers should switch logic path with authentication module calls
- Each of domain logic, resolvers, queries and mutations should be separately testable
- Resist the temptation towards automagic unless for simplest cases
The Future
- Versioning
- Update the types and resolvers
- Add to domain logic
- As long as input/output data shape is the same
- Fairly easy to switch from ORM calls to SQL, different ORMs, or replacing third-party API calls with homegrown ones
- Ensure separation of concerns/single responsibility principles among modules
Module (api/) | Responsiblity |
---|---|
lib/vehicles/context.py | Read/Write logic for all asser related things |
utils/vehicles/serializers.py | Conversion between GraphQL type to Python native type |
web/vehicles/types.py | GraphQL Types |
web/vehicles/resolvers.py | Functions that resolves a path by utilizing lib/asset/*.py functions |
web/vehicles/(queries|mutations.py) | Root queries and mutations for assets module |
Replace .py with .go or .ex if you will
What about the code?
Wait for it...!
You'll really have to wait :(
Thank You.
You guys rock!
Backend Developer's View on GraphQL
By Mafinar Khan
Backend Developer's View on GraphQL
The slides of my talk for GraphQL Toronto Meetup (September 2018) in Shopify. https://www.meetup.com/GraphQL-Toronto/events/254175984/
- 1,470