Today, it is possible to build backends for apps that allow for fast iteration, while being resilient and highly scalable from the get go. Jump to reference implementation.
We propose an architecture pattern which is composed of 3 factors:
Consider a traditional food-delivery application which moves to a 3factor architecture:
GraphQL APIs result in a much faster front-end developer workflow. In addition to speaking GraphQL, your API should also support the following 2 properties:
Refactor high-latency synchronous API responses to be reactive instead.
Instead of REST APIs, use GraphQL as much as possible to improve frontend app development.
Further, consider a naive GraphQL mutation to place an order (for a food-delivery application) that would have executed a multi-step workflow or orchestrated microservices. This would have taken a longer time to respond and increased the chances of failure.
Refactor this to an
atomic GraphQL mutation that just inserts an order and responds with an
order-id. Update UI based on
realtime workflow updates (via subscriptions) to the
order-id. The end user is confident that the order is placed and does not need complex error handling logic per request.
Remove workflow and orchestration state from your backend APIs and persist them into events. Your event system should have 2 properties:
Instead of writing a
place-order API endpoint (for a food-delivery application) that orchestrates
many upstream microservices by making API calls (like a workflow), emit events
for each state transition.
Your event system should deliver the events reliably to other microservices. This makes your application resilient to transient failures because the event is persisted and can be retried without affecting the entire workflow.
It makes your application resilient to larger scale failures also because the event system and your data store can easily be replicated across availability zones making application recovery straightforward.
Write business logic with serverless compute that is asynchronously triggered. This also mitigates the cold-start issue from causing slower perceived latency.
This also allows for rapid iteration in the business logic without impacting the GraphQL contract.
In your food ordering workflow, instead of writing a payment processing microservice that captures different failure modes, write a payment processing function that processes a payment or simply fails with an error code.
Suppose a payment fails, now you can insert a state/emit an event denoting failure with error code 1001. Now this event invokes a serverless function specifically aimed at resolving this error.
This architecture encourages having smaller cohesive functions which do “atomic” work as much as possible.
A 3factor app requires you to remove state from your code and put it in your datastore and in your event queues. Cloud vendors make it easy to scale and replicate your datastore and event-queues. Making your business logic asynchronous requires a proportional investment in your realtime GraphQL API to allow the end-user app to consume asynchronous information easily.
An interesting sidenote: A 3factor app’s architecture is analogous to the redux dataflow model on a react app, but applied to the fullstack.
The 3factor name is inspired from 12factor.net. 12factor.net, created 7 years ago by the folks at Heroku, is a guide/methodology for creating isolated, portable microservices for modern cloud platforms. Although the name is similar, 3factor.app is actually an architectural pattern.
A complete step-by-step reference implementation for a 3factor app is available at: github.com/hasura/3factor-example