Replies: 7 comments 14 replies
-
Hi @rivantsov ! Thanks for your feedback. It's much appreciated. I think you're correct that semantic information is derivable by humans from API docs and field names, so I won't spend too much time addressing that. However, I want to clarify that the Client Controlled Nullability (CCN) proposal does not affect SDL. The syntax introduced by the proposal is solely used by developers writing operations.
While I think there is some human-readable semantic information provided by CCN, I don't think operations are read nearly as much as SDL (and API documentation) so I don't think it's nearly as important as what it enables for tooling. That's why that was the primary focus of the original RFC, so I'm glad you included a section about tooling. I realized I didn't have the original RFC linked in the spec PR, so I've fixed that. Here's a link so you can read more about the motivations behind the proposal: https://github.com/graphql/graphql-wg/blob/main/rfcs/ClientControlledNullability.md The TL;DR relevant to you is that at Yelp, code generation was rendered practically useless. Developers needed to write wrapper types for every operation to handle all of the null-checking that was required. The Relay team saw similar problems at the company formerly known as Facebook, so they introduced a Relay-specific version of CCN before we even started talking about CCN. If you think those motivations aren't clearly translated over to the spec, let me know and I'll see what I can do to fix that :) I've got some questions about the use case you described in "4. Static constraints are overrated IMO", but I'll have to come back to that later today. |
Beta Was this translation helpful? Give feedback.
-
Ok, clearing the context that "It's all for client generators" - that changes the perspective. While I was listening to meeting recording, it all sounded like it's for human consumption. So my 'utility evaluation' ideas probably do not apply that much to CCN. But then I have more questions, and even doubts.
but I think you should add this, for clarity. Otherwise it sounds like both markers are equal in their function and independent of each other.
The client sends a request with ! on some nullable field. The server executes and ends up with null value for the field. The server then returns null for the field (it is nullable in schema!), but also adds an error to the response, with the path to the field. The client upon receiving response sees the error. So it needs to do the '?' part, nullify the containing object. I think it is not very complicated - take 'data' element, load into js object, follow the path of error (cut the last segment) and nullify it (parent object). Upon completion of all errors, the data object is serialized to json - and here you have it, the action of '?' done on the client. Then you can load this json into your data model. Again, not a js programmer, but - does this sound like something that can be done? If yes, then we can go with just ! modifier. |
Beta Was this translation helpful? Give feedback.
-
Haha! 😻 |
Beta Was this translation helpful? Give feedback.
-
You can encode that in GraphQL: interface Document {
id: ID!
title: String
body: String
# ...
}
type DraftDocument implements Document {
id: ID!
title: String
body: String
# ...
}
type PublishedDocument implements Document {
id: ID!
title: String!
body: String!
# ...
} There's been some discussion over the years about adding literal values (e.g.
You can encode that in the database with check constraints: create table documents (
id int primary key,
title text,
body text,
status text not null,
check (status <> 'PUBLISHED' or (body is not null and status is not null))
); This is, however, hard to extract meaning from programatically - you either need a human to interpret it or some complex code. |
Beta Was this translation helpful? Give feedback.
-
thanks for the response But the main problem with constraints like this - these are 'biz logic' rules. Which means they come from business/life. Which means they change. Get extended. And exceptions or special cases show up. Even if you succeed in encoding some flexible constraint, then pushed and made a Release - as soon as you're done, a biz-analyst/PM/customer shows up - "Ok, we need one special case, for this new (contract/invoice/ins-policy) doc type, this X might be null, and this Y is never null." You have no power to say NO, it is business, that's how the process works in real world, and you have to change it, ASAP. It is a constant stream of changes and exceptions from rule, and a 'static' constraint is NOT a good way to express it. I know how to make database constraints, I spent a lot of time in relational databases. But if we learned anything in the last 20 years it is this: do NOT put biz logic into database, ever. Because logic will be changing non-stop and it will become nightmare to maintain and re-deploy. And the sample conditional non-null constraint is biz logic, it comes from business. The same reasoning applies to your example in GraphQL. Expressing it in GraphQL - cool! but not actually helpful in real life, by the same reasoning as with databases. And looks to me the solution makes the model quite bloated, with extra type for each doc status. Introducing new status would require an extra type. The net conclusion again - Static type constraints are overrated. Not useless, but not as useful as it might seem. It's not worth inventing ways to express more complex type constraints, they won't be used much; just like fancy constraints in modern databases. PS Remember Eiffel progr language? the primary idea was contracts, static constraints, compile time validation. Did not work out. Life is too complex and changes too much to be put into static contracts. |
Beta Was this translation helpful? Give feedback.
-
Topic: Named ListsThanks for opening the discussion. There are some points I agree with, but I differ on some. Documentation
First of all, I don't think the restriction of the types behind the fields is always so obvious. For the invoice, this may be clear, but there are cases where it is not. Hypothetically, what if I never heard what the invoice is? (I know it's not realistic for the invoice, but it may make sense for another term in some domain). Plain documentation vs Types as documentation
Yes, that is correct. But types are also a way of documentation. The tedious thing about field documentation is its maintenance. When I update a certain behavior of invoices (e.g size), I have to simultaneously update the description of each field associated with the invoice in multiple parts. However we could define a list type with the name "Invoices" and its description, which we use for each field where we use invoices. this way we can also provide a description for all these fields. """
some specific behaviour for invoices
"""
list Invoices Documentation can't be type-checked
However, the documents cannot be used by GraphQL client and server tools for static constraint checking ( as discussed in Chapter 3.). Static constraints are not overrated
I see it a different way. As developers, we want to ensure that errors we can avoid at compile time are handled by the type system, not by us. Ideally, the program is a formal proof of our requirements (see Curry-Howard correspondence). However, if we specify an incorrect formula, our program will give the false impression of proof of correctness. In other words, our application will crash at some point in the runtime (in the worst case, in production).
In your example, I see that documents with the status "DRAFT" and "PUBLISHED" have different characteristics and should not be unified as one type instead of having two different types as @benjie represented. Use cases and Client benefits
First, this feature is beneficial not only for the clients but also for the servers since the named list automatically validates all list values that the server sends to the client. Second, some common types like ( |
Beta Was this translation helpful? Give feedback.
-
I have posted a feedback on original NamedLists thread: |
Beta Was this translation helpful? Give feedback.
-
Comments on some discussions at WG meeting on Jan 6 2022
Benjie - what a gorgeous CAT you have there! Too bad it looked like he didn't want to contribute, not a single 'meow' :).
My comments concern discussions of 2 items: 'Named Lists' and 'Client controlled nullability'. Costs/benefits analysis for expressing extra type constraints like list elements nullability. I absolutely agree that we must evaluate potential utility against added complexity. What I am concerned with is how we approach this benefits evaluation.
It seems to me, after listening and reading arguments, that we kinda assume that for an API the SDL schema doc (or introspection) is the ONLY information that will be provided to the consumer (human developer), so we need to express as much details as possible like nullability of elements deep inside multi-level lists. So the typical scenario is like this: the client programmer looks at schema doc, and starts programming the client based on what he/she sees in the doc. Thus the schema doc must provide as much details as possible about the types and fields and possible list contents, so there must be a syntax to express these extra constraints.
However, I think we are making a mistake here, and as a result the utility/usefulness of a feature is overrated.
1. Semantics of the types/model express natural constraints
The real world types and fields are NOT meaningless foos/bars, they have explicit real-world semantics which make natural constraints clear and obvious in most cases. If we have an Invoice Type with Items list property, it is clear that this list cannot contain nulls, it does not make sense. On the other hand, if I have tic-tac-toe board represented as 2-dim array:
then it is clear that individual cells are nullable, and all nulls is initial board. I am not saying that exact nullability notation is useless, but that its utility is limited, it just confirms what is already clear from semantics.
The same goes for other constraints like suggested in 'NamedLists', for ex (quote): "we cannot represent collections with certain constraints."; like "list contains at least 1 element". For an Invoice type as an example, it does not make sense to have invoice with 0 items. Thus 'non-empty' constraint is clearly derived from type semantics. The extra syntax expressing this non-empty constraint just confirms what is clear already.
2. There is always documentation for non-trivial API which describes semantics and constraints
Any non-trivial API is delivered with documentation that describes the SEMANTICS of the API and its business semantics that is not expressible in syntax in the schema doc. Even if this documentation is only in extended description fields in the schema. It is these docs that contain all detail information about types and restrictions and validation rules for the data types and fields, in context of 'business flow'. And these docs should contain clear info on list like Invoice.Items - can it be null or can contain zero items. It is not my wishful thinking that there must be docs - for non-trivial API the semantics and flows should be described somewhere, otherwise it is not possible to use the API.
3. Tools may benefit?
So these 2 factors (semantics and extra API docs) make the utility (value added) of extra syntax for constraints quite limited and marginal in most cases, specifically for humans (devs) that can understand model semantics and can read docs. However, there is another type of information users - TOOLS, that cannot read plain docs and don't understand semantics of the API, they use introspection only. Can they use extra type constraint info?
During discussion of the features, there came a suggestion to find real world cases (lists specifically of higher ranks) to evaluate the potential benefits. What I suggest here, given the limited utility of the constraints for human developers (that I argued previously), we should instead look at tools like Graphiql and client generators, and ask a question - how it will benefit them, and indirectly humans that use them? Looking at tools is simpler I think than hunting for real world uses of some list types.
And here I have doubts - that these constraints will benefit the tools, but I might be wrong.
4. Static constraints are overrated IMO
One more general consideration. In general, I think static type constraints (like null/nonNull) are overrated, and their possible impact on bugs in programming languages. There is wide-spread belief that since many of runtime bugs are NullReferenceException, then if we tighten the control of null values, and add static constraints on fields and variables that value may not be null then we'll reduce number of these bugs. This is the reasoning behind recent introduction of new c# features of 'non-nullable reference types'.
In my opinion, this is a false hope.
The problem is - in many business cases the nullability of a field depends on the containing document 'Status'. A document (entity, complex type instance) goes thru a lifecycle; at the end, the document like Invoice MUST have certain fields nonNull - but only when it is in some final state like 'Approved'. Initially, however, it starts life in a Draft state, when many of the fields are null or empty lists, and it goes thru the process of filling things out. As a result, we CANNOT enforce a static constraint like NonNull on many fields, because they are NULL initially.
The same goes for many other constraints. And many 'nullability' constraints depend on the invoice 'kind' or sub-type; there are many kinds, and each has it's own validation requirements.
Added: the conundrum becomes very apparent when you design database table for Invoice and alike doc entities. You have to make many columns nullable even if it does not make sense for biz. It is in fact frustrating to see that you CANNOT use non-null constraint and have database assist you in validation/consistency - just because the doc starts in some DRAFT state for some short time where things can be null.
From my experience, this is very common case, and it is THESE fields that are nullable initially, but should be assigned later, are the cause of NullReferenceExceptions down the road. These are real hard cases and hard bugs - the process expects Address to be filled but it's not. And static NonNull constraint cannot help, it cannot be used here.
So what I am saying - don't sweat over expressing precise constraints on fields and values, they are NOT so useful after all. This is from my 25 years of experience in the industry, I cannot bring up exact numbers obviously.
So to sum it up - if take all these considerations into account, the cost/benefit ratio of the features like 'NamedLists', or exact nullability of nested lists is quite LOW, at least it appears to me, given really non-trivial and potentially confusing syntax
thank you
Beta Was this translation helpful? Give feedback.
All reactions