react-apollo-domain
v2.0.0
Published
This is a small library that allows you to map your own domain entities to the results returned from a graphql operation. By domain entities we understand classes that wrap the mentioned results and can add extra functionality like computed properties or
Downloads
12
Keywords
Readme
React Apollo Domain
This is a small library that allows you to map your own domain entities to the results returned from a graphql operation. By domain entities we understand classes that wrap the mentioned results and can add extra functionality like computed properties or custom methods that operate with the entity data.
React Apollo Domain is designed to create the less amount of friction possible with your existing code and to be fully compatible with Apollo Client.
Installation
It's as simple as installing the npm package:
$ npm install --save react-apollo-domain
You can use React Apollo Domain in any of the environments where you can use React and Apollo Client.
Usage example
Out of date
Minimal changes are needed to existing codebases in order to start using this domain approach. Let's see a simple example that's going to demostrate everything you need to know in order to start using this library. Or if you're more of a seeing things in action guy, there's an example ready here.
Let's suppose we have the following graphql schema (taken from graphql-pokemon):
type Pokemon {
id: ID!
number: String
name: String
# some more properties not needed for the example...
attacks: PokemonAttack
evolutions: [Pokemon]
}
type Attack {
name: String
type: String
damage: Int
}
type PokemonAttack {
fast: [Attack]
special: [Attack]
}
First we start defining our own domain classes. The way this works is that every domain entity we define is going to be mapped to the same __typename
in graphql.
domain.js
import { DomainEntity } from 'react-apollo-domain';
// Define a domain entity that is going to wrap the Pokemon type
class Pokemon extends DomainEntity {
// custom method
toString() {
return this.name;
}
}
// Define a domain entity that is going to wrap the Attack type
class Attack extends DomainEntity {
// custom property
get fullName() {
return this.name + '-' + this.type;
}
}
// We don't need to define an entity for PokemonAttack because it's not
// a real entity, and more an utility type to hold both types of attacks.
// Finally, export our domain
export default {
Pokemon,
Attack, // We could even alias our domain to other typenames with => Alias: DomainEntity
};
Then, we need to add the domain cache and link to the client. DomainCache
is just a subclass of InMemoryCache
so both have the same behavior.
app.js
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { ApolloLink } from 'apollo-link';
import { setContext } from 'apollo-link-context';
import domain from '../domain';
import { createDomainCache, createDomainLink } from './react-apollo-domain';
const client = new ApolloClient({
link: ApolloLink.from([
middlewareLink,
createDomainLink(domain),
new HttpLink({ uri: 'https://fakerql.com/graphql' }),
]),
cache: createDomainCache(domain),
});
export default client;
Description
When developing web applications that interact through graphql, sometimes, you need to add extra functionality to the results you get from graphql. Either you want to calculate extra data based on the data you got or to add new methods that would allow you to perform operations with that data.
There are multiple ways of doing that, the most simple one is just adding extra methods to the component performing the graphql query. Other way would be creating an utils collection of functions that receives the data and returns the computed data.
The problem those approaches have is that in the first place, every time you want to compute the same data in a different component, you have to duplicate functionality. In the second case, every function can be very spread through the whole system and depending on the organization method you choose, very complicated to follow which function affects what data.
As a third option, we think those computations should be a property of the data itself, this way, every time you have access to that data, you'll have access to those computations. That has some benefits, for example, the children components don't need to know that the data is computed, everything related to an entity, is close to that entity instead of spread through a collection of functions, some computations can use other computations transparently to the user, etc. You can even use the computed properties to normalize the data passed between the back-end and the front-end.
Main concepts
The two basic principles of this library are:
- Everything that is a domain entity in the back-end, should be a domain entity in the front-end.
- And every computation based on the internal data of an entity, should be as close to that entity as possible.
With those two principles in mind, let's see what is a Domain Entity:
A domain entity is just a normal JavaScript class that encapsulates all the data received from a graphql operation. That class can define extra properties and methods that operate on said data. By default, if just the class is defined, the behavior is the same as the one of raw data.
Examples:
import { DomainEntity } from 'react-apollo-domain';
class Pokemon extends DomainEntity {
}
class Attack extends DomainEntity {
}
API
DomainEntity
Base class for a domain entity. Defines common behavior between all entities. Example:
import { DomainEntity } from 'react-apollo-domain';
class Pokemon extends DomainEntity {
}
createDomainLink()
Creates a link that will map the domain entities to every response to a mutation or subscription.
createDomainCache()
Creates a cache inheriting from InMemoryCache
that will map every domain entity to every read
operation.
License
MIT. See LICENSE for details.