HotChocolate Patching Types #4845
Replies: 8 comments 2 replies
-
@michaelstaib is there any planned time when this will be released? |
Beta Was this translation helpful? Give feedback.
-
Hi @allmightyse, not yet, I expect this more towards the end of 2021. |
Beta Was this translation helpful? Give feedback.
-
@michaelstaib is there any planned time when this will be released? |
Beta Was this translation helpful? Give feedback.
-
@PascalSenn Any idea if this will make its way into HC sometime this year? |
Beta Was this translation helpful? Give feedback.
-
Are there any examples or best practices for patching with Hot Chocolate? It's quite a common requirement (SO1, SO2), so having some guidance through documentation would be very helpful. |
Beta Was this translation helpful? Give feedback.
-
We have not implemented this yet. Its an open topic we are discussing every now and then but could not get consensus on. But a community package could easily implement this, all the hooks are there. |
Beta Was this translation helpful? Give feedback.
-
I have carefully read the spec and, really, it's kind of a very early spec. Actually, it would be very interesting to hear the real needs from projects with real examples.
But there's no notice in the provided spec if there would be an intermediary providing just a patch details in order to implement a custom "ApplyTo". According to the spec, everything is hidden under the hood of a single method "ApplyTo". That is not flexible and doesn't suit our needs. Also, I want to emphasize that in many cases, the patching is not so difficult and can be done ad-hoc. For that reason, I would suggest the folks asking here not to wait but just implement it in their own way. If you could give more details of your needs you might get some advice here about how to implement it, I hope, without too much difficulty. |
Beta Was this translation helpful? Give feedback.
-
I have just spent the last couple of days working on a source generator that automatically creates an input and update proxy model for a class. namespace ExampleProject.Models;
public class Contact
{
public int Id { get; set; }
public string Name { get; set; }
public string? Email { get; set; }
}
public class ContactTypeBuilder : GraphQlTypeBuilder<Contact>
{
public override void Configure(GraphQLTypeBuilderOptions<Contact> builder)
{
builder
.Ignore(_ => _.Id)
;
}
} The source generator traverses the syntax and builds the following models: Input model will mimic the nullability of the original class but will wrap the properties in an namespace ExampleProject.Models;
// Generated to `ContactInputModel.g.cs`
public record ContactInputModel
{
public String Name { get; init; } = string.Empty;
public Optional<String?> Email { get; init; }
}
// Generated to `ContactUpdateModel.g.cs`
public record ContactUpdateModel
{
public Optional<String?> Name { get; init; }
public Optional<String?> Email { get; init; }
} The trick is at the point of creating the model I also make a "Patch" method at the same time. It proceedurally creates the update methods depending on the property's properties - like the System.Text.Json source generator public record ContactInputModel
{
public String Name { get; init; } = string.Empty;
public Optional<String?> Email { get; init; }
public bool Patch(Contact original)
{
bool hasUpdates = false;
if (original.Name.Equals(Name.Value) is false)
{
original.Name= (String)Name.Value;
hasUpdates = true;
}
if (Email.HasValue && original.Email?.Equals(Email.Value) is false)
{
original.Email = (String?)Email.Value;
hasUpdates = true;
}
return hasUpdates;
}
}
public record ContactUpdateModel
{
public Optional<String?> Name { get; init; }
public Optional<String?> Email { get; init; }
public bool Patch(Contact original)
{
bool hasUpdates = false;
if (Name.HasValue && original.Name.Equals(Name.Value) is false && Name.Value is not null)
{
original.Name= (String)Name.Value;
hasUpdates = true;
}
if (Email.HasValue && original.Email?.Equals(Email.Value) is false)
{
original.Email = (String?)Email.Value;
hasUpdates = true;
}
return hasUpdates;
}
} This would mean that migration endpoints look like this: [GraphQLName("updateContact")]
[GraphQLType(typeof(ContactObjectType))]
public Contact UpdateContact([Service] ContactDao dao, int id, ContactUpdateModel input)
{
if (dao.GetById(id) is not { } contact)
throw new GraphQLException(new Error("Unable to find contact", "UPDATE_CONTACT_FAILED"));
return input.Patch(contact)
? dao.SaveOrUpdate(contact)
: contact;
} It needs some more work but it does work, I would be happy to contribute my code and services if you wanted to go down this route. |
Beta Was this translation helpful? Give feedback.
-
HotChocolate.Types.Patch
Introduction
A common thing of every application is partial updates of objects. Using GraphQL you can only create partial updates when using optional input types. In the mutation resolver you have to check the input type for all attribute that are set and then do the partial updates. This behavior is very generic and can occur many times in different places in the application.
Proposal
A new input type created for this use case:
PatchInputType<T>
. This abstraction allows to configure input types that offer patching abilities provided by the framework.How it could work shows the example below:
Patching scalars
The easiest thing to patch are scalars. Patching scalars just replace the scalar.
Patching lists
List could be patched in two different ways
Patch(x => x.List)
This would work the same as scalars do and just replace the list. The benefit of this approach is that input object and output object have the same structure.
PatchList(x => x.List)
This method of list patching would allow different ways of patching.
Examples:
set
append
removeIndex
replaceIndex
Patching objects
Objects can either be set or patched:
Patch(x => x.Object)
This would work the same as scalars do and just replace the list. The benefit of this approach is that input object and output object have the same structure.
PatchObject(x => x.Object)
This method allows patching of nested objects
Patching of Object Arrays
Patching of object arrays can be done in the same ways as arrays.
Patch(x => x.ObjectList)
This would work the same as scalars do and just replace the list. The benefit of this approach is that input object and output object have the same structure.
PatchList(x => x.ObjectList)
set
append
See Scalar List
append
removeIndex
See Scalar List
removeIndex
replaceIndex
See Scalar List
replaceIndex
allowPatchAll
removeById
replaceById
patchById
Patching Customization
What would an API be without customization? A framework is always limited.
It will work for 80% of the cases but unfortunately the feature we need is in the other 20%.
The Pareto Principal and Murphy's Law will strike eventually
If we provide this patching feature, it should really be able to handle all possible ways of patching in some way.
A few ideas are listed below:
Predicate
Predicate with get and set
IPatchingContext
Further Ideas
Connect
A way to create realtions between objects. For example, having a User and connect it with a friend
Object List
AllowWhereXY
A way to create realtions between objects. For example, having a User and connect it with a friend
How others did it
Prisma
Hasura
-
-
#-
GraphCMS
Gatsby
Beta Was this translation helpful? Give feedback.
All reactions