Data flow in Remote Operation
Farfetched is designed to deal with data on the remote source (e.g. backend server), so there is an abstraction to represent an operation on this data — Remote Operation. For now there are two types of Remote Operations: Query and Mutation.
Because Farfetched considers data on the remote source as untrusted, it is required to pass any response through a couple stages of validation and transformation before it is used in the application.
Flow of for any Remote Operation
Basic and specific factories
There are two types of factories for Remote Operations: basic and specific. Basic factories are used to create Remote Operations with a more control of data-flow in user-land, while specific factories are used to create Remote Operations with a more control of data-flow in the library. Specific factories are built on top of basic ones and are providing better DX for more specific use-cases.
E.g. quite often API is basically HTTP-endpoint, which responses to you with some JSON — Farfetched provides you createJsonQuery
for this case. This factory hides complexity under the declarative API and handles a lot of edge-cases for you
Basic factories
Specific factories
TIP
Data-flow control is a boring and complex task, so it is recommended to use specific factories in many cases to delegate this task to the library.
Data-flow in specific factories
Since only specific factories are allows Farfetched to have a full control of data-flow, in the following articles we will take a closer look to them. Basic factories work in the same way, but they require more attention from the user.
Request-response cycle
The first step is to send a request to the remote source and wait for a response. Because of Farfetched handles this stage internally, user-land code have to describe only the desired result of this stage and the library will perform the request-response cycle internally in the most optimal way.
Failed response stops the data-flow and returns control to the user-land code through .finished.failed
Event. Successful response continues the data-flow and passes control to the next step — response parsing.
Response parsing
Specific factories of Farfetched performs this stage internally, based on a use-case they were created for. In case of parsing error, the data-flow stops and returns control to the user-land code through .finished.failed
Event. Otherwise, the data-flow continues and passes control to the next step — contract application.
JSON example
createJsonQuery
and createJsonMutation
use JSON.parse
to parse the response and throw an error if the response is not a valid JSON. Because these factories handle this stage internally, they can optimize the parsing process in the most optimal way.
For example, if some when in the future JSON.parse
will be considered as a bottleneck, the library can replace it with a more optimal implementation without breaking the API. Your application would not be affected by this change, because it does not know anything about the implementation details of the library.
Contract application
Specific factories require explicit Contract because they consider the response as unknown
by default. So, the user-land code have to describe the contract of the response or explicitly use unkownContract
to preserve the unknown
type.
If parsed data does not satisfy the Contract, the data-flow stops and returns control to the user-land code through .finished.failed
Event with an error-message that is returned from the Contract. Otherwise, the data-flow continues and passes control to the next step — validation.
Validation
This is optional stage. It is performed by Validator and is used to check if the response is valid. If the response is not valid, the data-flow stops and returns control to the user-land code through .finished.failed
Event with an error-message that is returned from the Validator. Otherwise, the data-flow continues and passes control to the next step — data mapping.
Since Validator is a Sourced, it's possible to add some extra data from the application to the validation process. For example, it could be a user's session token:
const $session = createStore<{ userId: string } | null>(null);
const userQuery = createJsonQuery({
//...
response: {
validate: {
source: $session,
fn: (result, _params, sessionToken) => result.userId !== session.userId,
},
},
});
Data mapping
This is optional stage. So, you can define a mapper to transform the response to the desired format.
WARNING
Data mappers have to be pure function, so they are not allowed to throw an error. If the mapper throws an error, the data-flow stops immediately without any error handling.
Since mapper is a Sourced, it's possible to add some extra data from the application to the mapping process. For example, it could be a current language:
const $language = createStore<string>('EN');
const userQuery = createJsonQuery({
//...
response: {
mapData: {
source: $language,
fn: ({ result }, language) => ({
...result,
name: result.name.translations[language],
}),
},
},
});
Data-flow in basic factories
Basic factories are used to create Remote Operations with a more control of data-flow in user-land. In this case, the user-land code have to describe request-response cycle and response parsing stages. Other stages could be handled by the library, but it is not required for basic factories.
Summary
In this article, we have learned how data-flow works under the hood in Remote operations. We have also learned about two types of factories and how they differ from each other. Key points:
- Basic factories are used to create Remote Operations with a more control of data-flow in user-land, which allows you to have a very granular control over Remote Operation behavior.
- Specific factories are used to create Remote Operations with a more control of data-flow being in the library, which creates a better DX for you.
- Prefer specific factories in many cases to delegate data-flow control to the library.