Skip to content

Instantly share code, notes, and snippets.

@wtrocki
Last active July 23, 2020 15:53
Show Gist options
  • Save wtrocki/1434358599e7a78f1a15014ac8d2f3fa to your computer and use it in GitHub Desktop.
Save wtrocki/1434358599e7a78f1a15014ac8d2f3fa to your computer and use it in GitHub Desktop.

Simple case

const SOMETHING_CHANGED_TOPIC = 'something_changed';

export const resolvers = {
  Subscription: {
    somethingChanged: {
      subscribe: () => pubsub.asyncIterator(SOMETHING_CHANGED_TOPIC),
    },
  },
}

pubsub.publish(SOMETHING_CHANGED_TOPIC, { somethingChanged: { id: "123" }})

Simple case withFilter

See: https://github.com/apollographql/graphql-subscriptions

Using topics filtering

export const resolvers = {
  Subscription: {
    todoChanged(_parent, args, info): {
      subscribe: () => pubsub.asyncIterator(SOMETHING_CHANGED_TOPIC + "_" + hashFn(args.filter) || ""),
    },
  },
}

Publish:

pubsub.publish(hashFn(SOMETHING_CHANGED_TOPIC), { somethingChanged: { id: "123" }})

Ideally we need to have both approaches. hashFn is embeeded on compilation time which means we cannot handle this for cases like graphql-serve etc.

@craicoverflow
Copy link

craicoverflow commented Jul 23, 2020

Ideally we need to have both approaches. hashFn is embedded on compilation time which means we cannot handle this for cases like graphql-serve etc.

Since the subscription topic is set in the resolver, this would be hard to do without adding some configuration to the SchemaCRUDPlugin to cater for both use cases, right?

It seems to me like a potential security concern to pass the user ID as a subscription argument.

Maybe this should come from the context instead, and the server can set this in the context with contextCreator.

export const resolvers = {
  Subscription: {
    todoChanged(_parent, args, context): {
      subscribe: () => pubsub.asyncIterator(SOMETHING_CHANGED_TOPIC + "_" + hashFn(context.auth.userId) || ""),
    },
  },
}

cc @machi1990

@craicoverflow
Copy link

craicoverflow commented Jul 23, 2020

Suggested approach should work with MQTT and AMQ Online available on RHMI
With wildcards on topics
This means that there will be only single topic created
But it will route to dynamic topics based on wildcard

Interesting. Will this also work for an in-memory PubSub implementation such as graphql-subscriptions?

@wtrocki
Copy link
Author

wtrocki commented Jul 23, 2020

Since the subscription topic is set in the resolver, this would be hard to do without adding some configuration to the SchemaCRUDPlugin to cater for both use cases, right?

We can have generic hash method for investigation. It is to expose some filter arguments.

object = deepSortKeys(object)
JSON.stringify(object).replace('"',"_").replace(",","").replace(":",""_")

For common filters this will be very beneficial. Drawback of this option is that we need to be aware of the hashes when publishing
that makes this very hard to horizontally scale.

It seems to me like a potential security concern to pass the user ID as a subscription argument.
Maybe this should come from the context instead, and the server can set this in the context with contextCreator.

Please read this graphback documentation chapter: https://graphback.dev/docs/next/authentication/keycloak#dynamic-filtering
We can do anything we want - including pulling context etc.

Interesting. Will this also work for an in-memory PubSub implementation such as graphql-subscriptions?

Yes. Please check datasync starter:

https://github.com/aerogear/datasync-starter/blob/master/server/src/crudServiceCreator.ts#L61
and
https://github.com/aerogear/datasync-starter/blob/master/.openshift/amq-topics.yml#L20-L22
and
https://github.com/aerogear/datasync-starter/blob/master/.openshift/README.md

@machi1990
Copy link

Ideally we need to have both approaches. hashFn is embedded on compilation time which means we cannot handle this for cases like graphql-serve etc.

Since the subscription topic is set in the resolver, this would be hard to do without adding some configuration to the SchemaCRUDPlugin to cater for both use cases, right?

It seems to me like a potential security concern to pass the user ID as a subscription argument.

I guess a hashing function will take care of this and we could avoid situation where we'd use the raw "userId" or whatever as topics name.

Maybe this should come from the context instead, and the server can set this in the context with contextCreator.

export const resolvers = {
  Subscription: {
    todoChanged(_parent, args, context): {
      subscribe: () => pubsub.asyncIterator(SOMETHING_CHANGED_TOPIC + "_" + hashFn(context.auth.userId) || ""),
    },
  },
}

cc @machi1990

I am wondering what happens if we do not have the "auth" info i.e userId?

@craicoverflow
Copy link

I am wondering what happens if we do not have the "auth" info i.e userId?

You can have the same scenario when supplying userId as a filter argument. As example shows, it would default to "".

@machi1990
Copy link

machi1990 commented Jul 23, 2020

I am wondering what happens if we do not have the "auth" info i.e userId?

You can have the same scenario when supplying userId as a filter argument. As example shows, it would default to "".

Thanks for the response. I suppose defaulting to "", means this is the default topic and any subscription missing the "info needed for hashing" e.g userId, will be using his topic?

@craicoverflow
Copy link

object = deepSortKeys(object)
JSON.stringify(object).replace('"',"_").replace(",","").replace(":",""_")

What is object from this example, the filter argument coming from the subscription like {userId: {eq: "enda"}}? If that is the case, this will only work by using wildcards won't it? The create mutation resolver will publish the topic, and obviously this will not have the filter information available to it so it should use a wildcard in the topic path:

Mutation

const topic = 'CREATE_NOTE/filter/#';
pubSub.publish(topic, payload);

Subscription

const topic = `CREATE_NOTE/filter/${hashFn(filter)}`;
...
subscribe: () => pubsub.asyncIterator(topic)

need to set up a local OpenShift cluster to play with and learn about subscription topic wildcards.

@wtrocki
Copy link
Author

wtrocki commented Jul 23, 2020

I think that wildcard is defined only on server to define namespace for multiple topics.
When publishing we need to use explicit topic. This is major problem as we cannot rebuild it as you have mentioned.

The way I seen this done was always related to the user owning the object - then you publish to topic with his id.
This is very very specific to the auth implementation we have and it will be hard to have as generic provider.

As you have mentioned hashing will not work efficiently and in some cases will produce even worse results than in mem filtering.
I guess we can do inmemory filtering an call it a day + create follow up to extend our auth implementation to supply ownership subscriptiuons or similar concept.

@craicoverflow
Copy link

The way I seen this done was always related to the user owning the object - then you publish to topic with his id.

yep, saw it this way first after our conversation yesterday but wanted to get clarification on what object was.

This is very very specific to the auth implementation we have and it will be hard to have as generic provider.

Indeed.

I guess we can do inmemory filtering an call it a day + create follow up to extend our auth implementation to supply ownership subscriptiuons or similar concept.

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment