Is it possible to map a subscription parameter to an array at the mutation output? - graphql

I have a theoretical question. As I know subscription parameters must exist as a field in the returning type of the mutation. This means that the type of parameter must also match the type of the field in the returning object of the mutation. Am I right? Suppose I get an array with channels ids in the mutation response. I only send one channel id as a parameter in the subscription. Is it possible to map a subscription parameter to an array at the mutation output? If the channel id exists in the array (field channelsIds), the subscription must work. Is it possible to write this logic in the scheme itself, or is it technically impossible?
GraphQL schema:
schema {
mutation: Mutation
subscription: Subscription
}
type Mutation {
testMutation(input: TestMutationInput): TestMutationOutput
}
type TestMutationOutput {
channelsIds: [String!]!
userId: String!
userEmail: String
userPhoneNumber: String
}
type Subscription {
watchTestMutation(channelId: String!): TestMutationOutput
#aws_subscribe(mutations: ["testMutation"])
}

If I understand you correctly you want to filter based on if the mutation's returned value is in an array that is passed as an argument to the subscription. Sorry to say that is not possible at this time. Subscription filters only evaluate to true or false and cannot accommodate any logic other than that.

At the end of October 2020, I contacted AWS support for advice on this issue. I think this answer may be useful to someone, so I post their answer.
Please allow me to inform you that the use-case that you have
mentioned in the case is currently not possible via AppSync. I
understand that the lack of the feature may be causing inconvenience.
There is an internal feature request already with the AppSync team to
incorporate this feature and I have added a +1 on your behalf. It is
worth noting, that once this feature request is with the team, it will
be up to the team as to if/when this potential infrastructure feature
is implemented, and because of the limited visibility into the
progress of internal development processes, I won’t be able to provide
an ETA regarding its release. I would request you to keep an eye on
the what's new page or the AWS Blogs as all new feature requests and
enhancements are posted there[1-3].
However we can suggest a couple of workarounds in this case:
Filter the required fields on client side itself after receiving the values on the client-side from AppSync.
If the values to be filtered are very limited we can use a fake mutation made with the help of a resolver mapped to “None” Data
source. In this flow, we would create a lambda function that uses a
DynamoDB stream as the trigger. The Lambda function is triggered
whenever there's an update to the DynamoDB table.

We can then include logic in the Lambda function to filter the
required fields and perform a mutation to AppSync. In AppSync, the
mutation which was called by lambda would configured using a resolver
mapped to a “None” Data source. The None data source type passes the
request mapping template directly to the response mapping template.
And when we subscribe to this mutation, we will directly get the
filtered data from Lambda that was used to call this mutation. Please
refer to [4] for a step-by-step description of this process.
But please note that this workaround is cumbersome and would require a lot of changes if the required field values keep changing. Workaround 1(handling it on the client-side) is usually the preferred way to handle this use-case.
Resources:
[1] https://blogs.amazon.com/
[2] https://aws.amazon.com/new/
[3] https://aws.amazon.com/releasenotes/
[4] https://aws.amazon.com/premiumsupport/knowledge-center/appsync-notify-subscribers-real-time/

Related

Can apollo server return a partial success if one element of an array is invalid?

Say I have a query that returns an array of Customer objects, each one having an id, name, and email, all of which are non-nullable. My resolver loads the data from some source, maybe a database or maybe a downstream system. Most of the data that comes back is fine, but maybe for some reason we're missing the email address for a single customer.
Currently if my resolver just returns the array of all known customers, including the dodgy one, then the entire operation will fail with something like:
{
"data": {
"customers": null
},
"errors": [{
"message": "Cannot return null for non-nullable field Customer.email"
// More error stuff here
}]
}
I get that the validation is supposed to be strict, but now on my UI it fails to load the entire list of customers just because one of them is invalid. It makes it too easy for a single record to poison the entire database.
What I would like is a way to still return the error, but to also have the data field be populated with all of the other, valid Customer records. Is this possible to do with apollo server? Or do I need to manually validate all of the data before trying to return it from my resolver? That would be quite arduous as my real data structure is much more complex.
From this discussion, it seems there's no way to do exactly what I was asking (filter out the invalid records on the server side).
The right approach to this is to either:
Make the email field nullable, so the server can return every Customer, some of which may have incomplete data; or
Make the elements of the top-level Customer array nullable, so the server can return e.g. [customer1, null, customer3]
Option 1 means that the client gets access to more data on an error, so it could still show the rest of the customer's details even if the email is missing. However it makes the data structure less reliable as now anywhere that uses the email might need to do a null check.
Option 2 is a more aggressive failure, nulling out the entire customer because of a single bad field. But it makes it simpler for the client to filter out bad customer records at the top level, allowing the rest of the frontend code to be confident that every customer has an email address.
On that basis I think I prefer option 2.
You might be interested in this article: https://blog.logrocket.com/handling-graphql-errors-like-a-champ-with-unions-and-interfaces/
It talks about using union types to report result status rather than traditional GQL error reporting. Here's an example of how it might look in your case:
type Customer {
id: ID!
name: String!
email: String!
}
interface CustomerErrorInterface {
message: String!
}
type MissingCustomerEmailError implements CustomerErrorInterface {
id: ID!
}
union CustomerResult = Customer | CustomerErrorInterface
type Query {
getCustomers: [CustomerResult!]!
}
Then, on the client side, your query would look something like this:
query getCustomers() {
__typename
... on Customer {
id
name
email
}
... on CustomerErrorInterface {
message
}
... on MissingCustomerEmailError {
id
}
}
This allows you to maintain type safety and avoid nulling things that shouldn't be nullable, while still reporting meaningful results (both successes and error types) that are associated with the data you do have. Using an error interface rather than a concrete error type in the union allows you to leave the option for reporting new types of errors in the future in a backward-compatible way.
The difference between this and the "default" GQL way of error handling (with the top-level errors output) is that reporting the error in an interface allows still giving meaningful reference data (like the id field) in easy locations rather than needing to parse error messages, and different types of errors can provide different reference data as appropriate. Error interface type handling also better fits with regular GraphQL output type patterns (in my opinion) rather than needing to switch on an enum to know how to handle different types of errors.
Allowing null in the output list may accomplish something similar in this particular case, but it makes it impossible to know which piece of data has an error, especially if the data in question does not correspond directly to a user-supplied input that can be found through the path provided by traditional error handling.

How to derive values in apollo client

I have a few react components that mutate my server data and for now I have refetchQueries: [{query:myQuery}]. I need to restructured the data as a map for faster lookup time. How can I accomplish this? In Redux, I would have used reselect and in MobX I would have used #computed. As far as I can tell, apollo doesn't support this functionality yet.
I looked into:
#client directive, but this doesn't work for me since I have to compute the data on the server response.
reactive variables don't work either since I will have to change the variable everywhere I mutate the data, far from ideal.
There seems to be very little information out there about computed/derived values when using Apollo Client, the only reference I found was this one:
Apollo GraphQl Storing derived data
How about if you define in your schema & in your resolver some alternative (union) response structure? (not sure if this would work actually)
type Query{
books(mapBy:String): [Book]|JSON
}
so if you query (using it instead of mutation for simplicity)
query{
books(mapBy:"id")
}
it would return JSON
{
123: { __typename: "Book", name: "Dune"}
}
And if you don't want to return all of the Book fields in JSON, maybe pass extra param which would list actual structure you need.
Didn't encounter such problem myself yet, interesting. But otherwise, it should be done on client side.

when to use explicit relations - schema design

I am pondering about schema design with regard to explicit vs implicit relation when to...
for example:
in an imaginary schema with 2 custom types author and post, each with several properties, A post type can reference an author in 1 of 2 ways:
explicit: having an Autor type property
implicit: having a scalar value that indirectly points to the author
when designing a shema. what should be my compass in this kind of desicion making?
thanks in advance
There's absolutely no value to the client in only returning the ID of a related resource when you could just expose a field that would return the entire resource. Exposing only the ID will mean the client will have to make subsequent requests to your service to fetch the related resources, instead of being able to fetch the entire data graph in one request.
In the context of other services, like a REST API, it might make sense to only return the ID or URL of a related resource. This is because in those cases, the payload is of a fixed size, so returning every related resource by default can quickly and unnecessarily bloat a response. In GraphQL, however, request payloads are client-driven so this is not a concern -- the client will always get exactly what it asks for. If the client needs only the author's ID, they may still fetch just that field through the author field -- while allowing a more complete Author object to be fetched in other requests or by other clients.

How to use a Apollo GraphQL query result as the input of another one? Aka: request chaining

I know this has been asked a couple of times before, but I have found no definitive solution to whether this is possible with GraphQL. And I have a strong feeling this should be possible as it should be relatively easy to implement due to GraphQL queries running sequentially in Apollo.
I have a situation where I'm doing a GraphQL mutation first on the client, and then immediately after doing a query which uses the results from the previous query. This causes a needlessly long response time waiting for the server to respond to both requests. The requests look like this:
mutation createWebSession($authId: ID!) {
webSession: createWebSession(authId: $authId) {
token
userId
}
}
query listUserPaymentMethods($userId: ID!) {
userPaymentMethods: paymentMethods(userId: $userId) {
id
}
}
I know that one simple band-aid solution to avoid making 2 round trips to the server is creating a new single GraphQL mutation endpoint that does both services on the back end. But that seems to defeat the purpose of writing modular, reusable GraphQL endpoints. As such, I'm curious if someone knows if Apollo GraphQL supports a cleaner way to chain 2 requests in which the results from the previous one are available to the next one as inputs.
Any help would be greatly appreciated, thanks.
This is a limitation that specific to GraphQL in general. There is no way to do this in a single request for a couple of reasons:
While a GraphQL document may include any number of operations, only a single operation will be executed. If a document includes multiple operations, all operations must be named and the request must include an operationName parameter that specifies which operation to execute. In other words, while you can combine multiple queries into a single operation, or multiple mutations into a single operation, you cannot cannot mix-and-match queries and mutations.
Given any two fields that share the same "parent" field, both fields will resolve at the same time. The only exception to this are root level mutation fields, which do resolve in sequence. As such, GraphQL does not support any syntax that would let you reference another field and use it as the input to some argument.
One possible workaround is to include a field in your createWebSession payload type whose field is the Query type. I illustrate this approach in this article as a means of refetching queries but it would work for what you're trying to do as well.

Updating data from protobuf

I'm building a microservice system with multiple disconnected components, and I'm currently trying to find out how to implement knowing which fields on an object should be updated based on the protobuf data provided.
The flow is this:
The client sends a JSON-request to an API.
The API translates the JSON-data into a protobuf struct, which is then sent along to the microservice responsible for handling it.
The microservice receives the data from the API and performs any action on it, in this case, I'm trying to change a single value in a MySQL table, such as a client's email address.
Now, the problem I have is that since protobuf (understandably) doesn't allow pointers, the protobuf object will contain zero-values for everything not provided. This means that if a customer wants to update their email address, I can't know if they also set IncludeInMailLists to false - or if it was simply not provided (having its zero-value) and shouldn't change.
The question is: how will I - from the protobuf object - know if a value is expressively set to 0, or just not provided?
My current solution is pretty much having a special UpdateCustomer-object which also has an array of Fields specifying which fields the microservice should care about, but it feels like bad solution.
Someone must have solved this better already. How should I implement it?
Protobufs field masks are one way.
https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.FieldMask
https://github.com/golang/protobuf/issues/225
But if you are using grpc then there's a (sort of) built in way.
Grpc wrappers
Since proto3 (protobufs v3) there's been no distinction between a primitive that is not set, and a primitive that's been set to the "zero value" (false, 0, "", etc).
Instead you can use objects or in protobufs language a "message", as objects can be nil / null. You've not mentioned what language you are working in but hopefully these examples make sense.
Given an RPC service such as:
import "google/protobuf/wrappers.proto";
service Users {
rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse)
}
message UpdateUserRequest {
int32 user_id = 1;
google.protobuf.StringValue email = 2;
}
message UpdateUserResponse {}
Note the import "google/protobuf/wrappers.proto"; is important.
It given you access to the google protobufs wrappers source code here. These are not objects that have methods that allow you to test for presence.
Grpc generated code in java gives you methods such as .hasEmail() which returns true if the value is present. The getter on an unset value will still return you the zero value. I think the golang version uses pointers that you can test for nil instead of an explicit hasX() method.
More info / discussion in this github issue

Resources