Skip to content
This repository has been archived by the owner on Dec 19, 2019. It is now read-only.

Data loading workflow [DRAFT]

Valerii Naida edited this page Apr 8, 2019 · 24 revisions

Workflow

  1. Resolver: [FieldConfigInterface, QueryContextInterface, QueryInterface, QueryInterface]
  2. Search Criteria creating [query, query parametrs, query context] => SearchCriteria
  3. Data searhing [SearchCriteria] => matched identifiers
  4. Data loading[query info, matched identifiers] => loaded data

Resolving

ResolverInterface - entry point from an application

interface ResolverInterface
{
    public function resolve(
        FieldConfigInterface $fieldConfig,
        QueryContextInterface $queryContext,
        QueryInterface $query,                
        array $parentResolvedValue = []
    );
}

Ideally, initialize and pass just what resolver needed

FieldConfigInterface - represents 'field' GraphQL config element

interface FieldConfigInterface 
    extends \Magento\Framework\GraphQl\Config\ConfigElementInterface

QueryContextInterface - represents query context data

interface QueryContextInterface
{
    public function getStore(): StoreInterface;    
    public function getUser(): UserInterface;
}

Open question:

  1. Immutability with setExtensionAttribues (for example for collect warnings)
  2. Returning whole object or just identificator (store_code, user_id)

QueryInterface - represents query structure and parameters

interface QueryInterface
{
    public function getStructure(): ResolveInfo;
    public function getArguments(): array;	
    public function getVariables(): array;
}

array $parentResolvedValue - contains values resolved by parrent resolvers

Could be used as data container between resolvers

Data searching/matching

SearchCriteriaFactoryInreface || SearchCriteriaBuilderInreface - responsible for Search Criteria creating

interface SearchCriteriaFactoryInreface
{
    public function create(
        QueryInterface $query, 
        [QueryContextInterface $queryContext]
    ): SearchCriteriaInterface;    
}

Could has state for resolving N+1 problem (descibed bellow).

DataSearchInterface - responsible for matching and searching data

interface DataSearchInterface
{
    public function search(
        SearchCriteriaInterface $searchCriteria
    ): array;    
}

Open question:

  1. A format of output data
  2. Work with current search API or using a lighter version
  3. Covering indexes (when the index contains enough data and don't need to load data from raw source)

Data loading

DataProviderInterface - responsible for loading data from raw storage

interface DataProviderInterface
{
    public function load(
        QueryStructure $queryStructure, 
        QueryContextInterface $queryContext, 
        array $identifiers,
        array $parentResolvedValue = []
    ): DataInterface;    
}

QueryOutputProcessingInterface - responsible for processing data before output (for example set warnings to result)

interface QueryOutputProcessingInterface
{
    public function process(DataInterface $data): void;    
}

Base implementation works with \GraphQL\Executor\ExecutionResult (webonyx)

N + 1 problem

// Child resolver
$this->searchCriteriaBuilder->addFilter(...);

return $this->valueFactory->create(function () use ($query, $queryContext) {
    $searchCriteria = $this->searchCriteriaBuilder->create();
    $searchResult = $this->dataSearch->search($searchCriteria);
    $data = $this->dataProvider->load($query, $queryContext, $searchResult);
    return $data;
});

Extensibility

  • Resolver could be mutual for all queries: register 'DataSearchInterface' and DataProviderInterface per GraphQL query
<type name="Magento\Framework\GraphQl\Query\RequestMapping">
   <arguments>
       <argument name="categories" xsi:type="array">
           <item name="deferred" xsi:type="bool">false</item>
           <item name="data_filter" xsi:type="class [not object]">CategoryDataSearch</item>
           <item name="data_provider" xsi:type="class [not object]">CategoryDataProvider</item>
           <item name="output_data_processor" xsi:type="class [not object]">CategoryOutputDataProcessorChain</item>
       </argument>
   </arguments>
</type>
public function resolve(...)
    {
        // ...
        if (false === $this->queryConfiguration->isDeferred()) {
            $searchCriteria = $this->searchCriteriaBuilder->create();
            $searchResult = $this->dataSearchRegistry->get($queryId)->search($searchCriteria);
            $data = $this->dataProviderRegistry->get($queryId)->load($query, $queryContext, $searchResult);
            $processedData = $this->dataOutputProcessorRegistry->get($queryId)->process($data);
            return $processedData;
        }
    }