Open asishallab opened 4 years ago
Summary and update taken from Slack communication
Distinguish between generic models and generic associations. A generic model has standard resolvers and a "standard" data model layer, except all data model layer functions throw a 'not implemented error'. A generic model can have our standard associations to_one and to_many using foreign-keys. The respective field resolvers and model functions would still be generated. Those that end up in the generic data model layer will throw a not implemented error. Additionally add support for generic_to_one and generic_to_many associations. If these are specified, and note, that they should even be usable inside the other non-generic models, the field resolvers still get generated as normal without expecting foreign-keys. So the line this.employer_id = input.addEmployer would not appear. Also these field resolvers only perform the authorization, validation of input, and record-limit diminishing, but invoke a "Impl" function in the data model layer. This is exactly as the version of Cenzontle before our refactoring. Every field resolver invokes a namesake in the model that is called the "Impl".
Methodology: Generic models are a must. This would be the refactoring done for our previous web-service storage type. In this case no generic associations are supported. But the standard to_one and to_many should ideally still work. Think about how much more work generic associations would be. Could we even make them available in non-generic data models? How would that work? Try to summarize your ideas in a software design using our standard pseudo-code specifications. You can write a new document or use the existing spec.
In all read resolvers, i.e. root and field (associations), Cenzontle by default generates security and efficiency checks. These are:
See the template for e.g. "standard" SQL data models for details on Cenzontle's default behaviour.
In the case of write operations, especially on associations adhere to our specification. Make sure the respective create and update root mutations still perform the security and efficiency checks as defined in the spec before invoking implementations in the data model.
Consider the following example from our specification:
// root mutation (resolver)
// e.g.
addPerson( input, context ) {
let inputSanitized = sanitizeAssociationArguments( input, /* assocArgNames */ )
// Security check
checkAuthorization(...) // standard create permission on "Person" model as before
checkAuthorizationOnAssocArgs( inputSanitized, context, associationArgsDef )
// check if association args don't exceed record limit
checkAndAdjustRecordLimitForCreateUpdate( inputSanitized, context, associationArgsDef )
// check if association args actually point to existing IDs
// MAKE SURE THAT IN CASE OF A REMOTE DATA MODEL (DDM, cenzontle-web-server, generic web-service)
// that the association related args in input are ignored or removed, i.e. not processed.
// MAKE SURE THE
validateAssociationArgsExistence( inputSanitized, context,
// HANDLE PERSON ATTRIBUTES
// models.Person.addOne uses named function arguments, to
// only receive Person scalar attributes
let theNewBaby = models.Person.addOne( sanitizedInput )
// In Case of the DDM the above line passes the input the a responsible adapter!
// HANDLE ASSOCIATIONS
theNewBaby.handleAssociations()
}
Note that because we do not expect generic associations to use foreign keys, maybe the above validateAssociationArgsExistence
cannot be invoked? Check that. If it has to be omitted make the code generator write a "warning" code comment at that line, so the user knows what to add.
Associations of distributed data models (DDMs) can be distributed as well. In that case the security and efficiency checks are more elaborated, because responsible adapters have to be found for the associated records' identifiers (see adapterForIri
). However, generic associations only know two things from their definition, not more. Firstly the type of the associated record and secondly that this associated data model has some identifier attribute. Because generic associations don't guarantee the presence of foreign keys the above security and efficiency checks cannot be done in the standard way, simply because Cenzontle does not know where to get the associated records' identifiers from - again: there is no guaranteed foreign key.
Thus generic associations cannot be distributed in the way Cenzontle handles standard to_one
or to_many
distributed associations! While for our standard to_one
and to_many
associations the targetStorageType
and keyIn
arguments imply that there are different sub-types of these standard associations, this is not the case for generic associations.
A generic association is a generic association is a generic association..
Now, that this has been understood, we can define how generic associations should be implemented for DDMs. Basically, they stay exactly the same as in any other model.
The only noteworthy detail here is, that no code will be generated in the adapters. Generic associations will be handled directly in the DDM model module itself.
Adapt error handling as in issue #123
In detail this means that the data model layer functions should receive a new argument benignErrorReporter
and generate the JSDoc @parameter
tag accordingly. This tag explains the programmer that the benignErrorReporter
can be used to generate the standard GraphQL output { error: ..., data: ...}
. If the function reportError
of the benignErrorReporter
is invoked the server will include any so reported errors in the final response, i.e. the GraphQL response will have a non empty errors
property.
Functions in the data model layer affected by this are all root-reader and root-writer functions:
Because our default to_one
and to_many
associations invoke the associated data model's root reader the above already takes care of passing on the benignErrorReporter
. Association writing is handled by the so called "infix" functions, which are the add_<Assoc>
and remove_<Assoc>
resolvers and their respective implementations. As long as generic models make use of these, the beignErrorReporter
is thus already passed on to the data model layer as well.
All generic associations invoke "Impl-Functions" in the respective data model modules. This goes for reading and writing generic associations. In these cases the resolver needs to initialize a beignErrorReporter
and pass it as an argument to the respective data model layer function invoked. These functions are the "Not Implemented Error"-functions. Here the same as above has to be done:
benignErrorReporter
@parameter
description of itGeneric data models and generic associations do not require a separate template for the generated resolvers. Merge into a single template
We want the user / programmer be able to write their own code to resolve data models and their associations. The specification is to treat generic models and generic associations separately. The resolvers and schema should largely remain unchanged, i.e. the default case generated by Cenzontle. The respective resolver functions should invoke so called "Impl" functions in the data models.
For further details on the spec see our markdown specification.
Also see the summary shared in our Slack channel written to give more details on the above (older) specification. See screenshot below.