leangen / graphql-spqr

Build a GraphQL service in seconds
Apache License 2.0
1.09k stars 181 forks source link

Support for discriminatory updates? #407

Closed x80486 closed 1 year ago

x80486 commented 3 years ago

I'm currently using GraphQL SPQR with the Spring Boot starter. I'm trying to find a way to support partial updates using the same (domain model) class, but I can't find a way to make it work.

Basically, what I'm trying to do is to have a class that's used for updates to a given model:

public class UpdateCommand {
   private String status;

   private String version;

   // ...
}

So far, if I would like to use the same class for partial updates, I can't tell which value between status and/or version is meant to be updated because null doesn't really help there. I can't assume that the presence of null is just the fact that the field in question is going to be reset (as in unset) to back to null or just not updated.

I'm aware that this is an issue with Java and in some measure, this doesn't apply here 😏

kaqqao commented 3 years ago

As you yourself noted, this is a general impedance mismatch with Java. One thing you can do is make the fields Optional. If I remember correctly, in that case you'd get an empty optional when an explicit null is passed and null when nothing was passed. I know this sounds backwards, but that's how all JSON libraries in Java (or at least the ones by default supported SPQR) decided to work. It is not exactly pretty having Optional all over, but I see now other way to generically solve this.

x80486 commented 3 years ago

There is a somehow interesting implementation in the graphql-java-kickstart/graphql-java-tools repository. I wouldn't like to have class members declared as Optional, but if that works, I'll give it whirl for the time being 🙌🏻

It's interesting that this is not a hot subject in GraphQL (and typed languages) 🤔

Feel free to close this one if there is really nothing you could do in SPQR to address that.

kaqqao commented 3 years ago

There's a few things to unpack here. Seems like you're trying to implement anemic mutations which are generally an anti-pattern in GraphQL. But you might have no other choice. Or maybe I'm just wrong and that's not what you're doing at all 😅

There's an alternative approach in SPQR that might be useful to you. If you deconstruct the input, e.g. instead of

@GraphQLQuery
public Book update(Book input) {...}

you have

@GraphQLQuery
public Book update(String title, Author author) {...}

and tell SPQR to generate Relay-compliant mutations:

generator.withRelayCompliantMutations()

SPQR will generate a specific input type for each mutation that doesn't already take a single object.

The Undefined stuff you linked, is really a tri-state value, effectively like Optional if you allow null. But with a huge down side of being a custom type that has to pollute your domain model and needs custom handling for de/serialization.

x80486 commented 3 years ago

I'm currently dealing with an old (really old) system where everything is quite loose (in terms of validations) and updates happen in a single workflow, hence the need for a big-bloated object with optional members. On the other hand, while you can work with aggregates on the border, it's not really useful since they tend to have more fields/members than those models (you name it: DTOs, etc.) you use for input data. In that sense, I don't find Undefined as a pretty bad design, because it's something that's used on the border, and propagated into proper domain models later.

I'm just introducing a way to have an API, and probably modernize the user interface at some point.

I'll definitely check that approach with generator.withRelayCompliantMutations() also 👍🏻