-
Notifications
You must be signed in to change notification settings - Fork 59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Significant refactor to several annoying inhibitors to stability #352
Significant refactor to several annoying inhibitors to stability #352
Conversation
266bd4b
to
beba59a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing work as usual, thanks for chipping away at this! Definitely a tough one to peer review as a single commit, but I appreciate the effort to create a changelog - that helps a bit ;)
SchemaModelInterface now must declare getPropertyForField (gql field -> object property mapping)
SchemaModelInterface now must declare getSchemaContext and getModelConfiguration
I don't understand this layer enough to determine if that's a good change. Might need to write a bit of an architecture overview doc to get my head around it.
BuildState API is gone. Use new SchemaContextProvider in resolvers. Pass SchemaContext
objects to the small number of classes that needed it in their internals.
Yep that's a great change, a lot more explicit and testable.
How will that work when the schema build is performed outside of the production environment, e.g. during CI when creating a deployment package? That environment won't have access to the production |
Allow Behat smoke test failures for now Upstream is broken, and it's out of our control to fix (Chrome bug) - see silverstripe/silverstripe-asset-admin#1163 ENHANCEMENT: Code gen stores entire config array Context provider refactor Refactor nested plugins as a service Get rid of build state Get builds working Sorting and filtering working with new context providers simplify dataobject resolve, get rid of default resolvers Rebase, tests passing New ResolverFailure, resolverComposition for better error handling Fix schema context psr-4, queryhandlerinterface contract Fix aliases in create/update, add massive integration test Linting Add test for aggregate function
Allow Behat smoke test failures for now Upstream is broken, and it's out of our control to fix (Chrome bug) - see silverstripe/silverstripe-asset-admin#1163 ENHANCEMENT: Code gen stores entire config array Context provider refactor Refactor nested plugins as a service Get rid of build state Get builds working Sorting and filtering working with new context providers simplify dataobject resolve, get rid of default resolvers Rebase, tests passing New ResolverFailure, resolverComposition for better error handling Fix schema context psr-4, queryhandlerinterface contract Fix aliases in create/update, add massive integration test Linting Add test for aggregate function
f02a4ad
to
6375885
Compare
…n-assets situations
OK, now:
Why not add the transcriber event in only
|
I've sent a (very straightforward) devtools PR for this: silverstripe/silverstripe-graphql-devtools#33 |
If a Schema key isn't found, there's no reason to silently create it, since it'll just lead to more confusing behaviour further down the execution
Schema->exists() *only* works when processTypes() has been called, which only happens during save(). Checking for exists() beforehand will just silently fail to build the schema. This wasn't noticed until now because the admin schema defines custom queries directly, and is not reliant on processTypes to transfer the inferred queries from ModelType. Note that there's already a check that you have some queries defined in Schema: "Your schema must contain at least one type and at least one query". Which actually kicks in now during save(), after removing the continue statement.
Following UserContextProvider which has a $member = null signature.
Also fixed frameworktest with silverstripe/silverstripe-frameworktest#84 |
e71bd40
to
851908a
Compare
* New StorableSchema layer elimintates unpredictable state of Schema through process(). The readonly value object returned by getStorableSchema() is expected to be a complete set of primitive objects that are consumable by a storage service. * SchemaFactory is now SchemaBuilder, which has four clearly defined functions, and eliminates the confusion around using `Schema` at query time: * read(string $schemaKey): ?SchemaContext -- Get the cached configuration of a Schema. Useful for resolvers * boot(string $schemaKey): Schema -- Load all the configuration in to a Schema object. Useful for the configuration/build process. * fetch(string $schemaKey): ?GraphQLSchema -- Retrieve a queryable graphql-php Schema object from the storage service. Useful at query time. * build(Schema $schema, bool $clear = false): GraphQLSchema -- put the `Schema` through a storage service and return its queryable schema * SchemaStorageInterface is now the concern of `SchemaBuilder` rather than `Schema`, which results in a diminished API surface for `Schema`. * Validation of Schema moved to `StorableSchema`, as this is the only time the schema truly needs to be valid (before storage) * Type/field mapping done just-in-time for schema storage * Prefer event dispatcher over extend() hooks in controller
851908a
to
9a2dd37
Compare
Test failure is due to the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactoring the StorableSchema
as a value object is great, much cleaner. I'm still a bit confused why we need a Schema
that's separate from a SchemaBuilder
during the build phase. Feels like this could be inlined into SchemaBuilder
to avoid confusion between the two "schema" types which perform very different objectives. But I don't think it's worth extra effort at this point, you describe the responsibilities pretty clearly in PHPDoc - and we've already identified the need for an architecture overview doc.
How will that work when the schema build is performed outside of the production environment, e.g. during CI when creating a deployment package? That environment won't have access to the production assets/ folder.
I don't think your explanation covers it. The "run cms without assets module" edge case is worth covering, but I'm more concerned about building the schema during CI. If this <schema>.types.graphql
file is created anywhere other than PUBLIC_PATH
, it simply won't be accessible by the browser. You removed the dynamic fallback which is fine in general (since creation is part of a build that's a necessary precondition anyway). But you also removed the dynamic passthrough of a file's contents when they don't live in PUBLIC_PATH
.
That's important to resolve, because it means that CI-based builds would break admin/assets
. The schema cache files exist on the webserver(s) because they're part of the production deployment. The types file doesn't, because it's either stuck in CI on PUBLIC_PATH
(which will get filtered out of the deployment artefact), or because it's in a path where the webserver can't directly serve it without going through file_get_contents()
.
Just noted the overlap with silverstripe/silverstripe-framework#9688 - by merging this PR we're making a stronger (not yet semver binding) commitment to supporting |
Was mixing graphqlSchemaBuild and graphqlTranscribe keys, which lead to errors in test execution.
fd9b155
to
7af1294
Compare
Significant refactor to several annoying inhibitors to stability
Summary
Whilst working on the upgrade for silverstripe-gatsby to v4, I came across a lot of things that were slowing me down, and it just ended up being a rabbit hole, but these are all really solid, worthwhile improvements IMO, and to the extent that they break APIs, they're very unlikely to be relied upon by current users.
Issues addressed/resolved
Dependent PRs
TODO
Ingo: Ensure Behat tests are passingThey're not, but its likely unrelated. Let's see what CI says.Changelog
Refactor context provider API so that they now plug into
QueryHandlerInterface
. Theycan set and get their own context based on their own intnernal logic. Before:
$user = $context['currentUser'];
after:$user = UserContextProvider::get($context)
BuildState
API is gone. Use newSchemaContextProvider
in resolvers. PassSchemaContext
objects to the small number of classes that needed it in their internals.
AbstractNestedInputPlugin
is gone. This never made any sense to tie somethingso utilitarian to an arbitrary inheritance chain. It has been refactored as
NestedInputBuilder
and is far more single-purpose. No longer concerned with model and field mapping, but rather
just building recursive input types and adding them to the schema.
Refactor
QuerySort
,QueryFilter
to use input builderField mapping is no longer exhaustively encoded in the resolvers. New
SchemaContextProvider
allows resolvers to look at the schema as persisted to aPHP array in the code gen and invoke methods like
$schema->mapField($type, $fieldName)
No more
resolveContext
for DataObject\Resolver since field mapping is handled externally nowMigrate type
Controller::introspectTypes
to standalone service (SchemaTranscriber
)invoked through event dispatcher
Remove
IntrospectionProvider
extension. Static only. Leaving it to the user to decide never made any sense, and it just slowed down the UI in the CMS. Now that we're doing code gen, it seems like the static solution is more consistent.Remove
ModelConfigurationProvider
: Didn't make any sense. Models are nowresponsible for fetching their own configurations.
Remove
defaultResolver
: Didn't make any sense. Just set a defaultresolver
property and allow it to be overriden.
Resolver discovery is now lazy to avoid race conditions in config. If resolvers
are null at code gen time, discovery is invoked and applied.
Controller now only accepts a schema key and uses
SchemaFactory
to boot.Schema::quiet()
is gone. Schema is quiet by default to better support autobuildingand test suites. Opt into verbose with
setVerbose(true)
.CSRFMiddleware
andHTTPMethodMiddleware
now rely on new ContextProvidersto better encapsultate their state concerns.
Better error reporting:
ResolverFailure
which express tons of usefulcontext, including the query path, the field, the type, the args, and most importantly,
the resolver chain. No more guessing which closure failed.
resolverComposition
field in encoded types.variable assignment to save code execution and comments to show closing bracket relationships.
ComposedResolver is now stateful, mostly for diagnostic purposes. Before:
ComposedResolver::create(): Closure
after:ComposedResolver::create($list)->toClosure()
All resolvers use
ComposedResolver
for consistency, even when no middle/afterwaresComposedResolver
no longer needs separate arguments for primary, before, and afterresolvers. Just a stack that leaves ordering a concern of the code composing the class.
(this was a @todo annotation FWIW)
SchemaModelInterface
now must declaregetPropertyForField
(gql field -> object property mapping)SchemaModelInterface
now must declaregetSchemaContext
andgetModelConfiguration
New
hasNativeField
inFieldAccessor
helps with knowing what fields arefilterable/sortable. Basically ORM insight.
Fields using the
model
property (a bit of a polymorphic hack to supportcases like CMS needing to know what the SiteTree model is named), will
get just-in-time assignment before code generation time to prevent race conditions
in the config.
New suite of relational
FakeProduct
objects for testing nested queries, filters, mutations.New top-to-bottom integration test that creates, updates, and reads nested data structures with filters/sorting, field aliases, etc.
NB:
CodeGenerationStore
is now responsible for type mapping and field mapping. Thisis a leaky abstraction as it's tightly coupled to SchemaContext now.
New
StorableSchema
layer eliminates unpredictable state ofSchema
throughprocess()
. The readonly value object returned bycreateStorableSchema()
is expected tobe a complete set of primitive objects that are consumable by a storage service.
SchemaFactory
is nowSchemaBuilder
, which has four clearly defined functions, and eliminates the confusion around usingSchema
at query time:getConfig(string $schemaKey): ?SchemaContext
-- Get the cached configuration of a Schema. Useful for resolversboot(string $schemaKey): Schema
-- Load all the configuration in to a Schema object. Useful for the configuration/build process.getSchema(string $schemaKey): ?GraphQLSchema
-- Retrieve a queryable graphql-php Schema object from the storage service. Useful at query time.build(Schema $schema, bool $clear = false)
: GraphQLSchema -- put theSchema
through a storage service and return its queryable schemaSchemaStorageInterface is now the concern of
SchemaBuilder
rather thanSchema
, which results in a diminished API surface forSchema
.Validation of Schema moved to
StorableSchema
, as this is the only time the schema truly needs to be valid (before storage)Type/field mapping done just-in-time for schema storage
Prefer event dispatcher over extend() hooks in controller
SchemaContext
is nowSchemaConfig
Schema
has newgetState()
for persisting state during buildImproved handling of "empty" schemas that shouldn't be built, but also shouldn't error (e.g.
default
out of the box)