Coder Social home page Coder Social logo

Comments (26)

raheelkhan avatar raheelkhan commented on May 13, 2024 1

Hi @michalkvasnicak

Thanks for your reply.
Let me try to explain you steps.

My server is running as per the documentation in this repository.

Serverless: Offline [HTTP] listening on http://localhost:3000
Serverless: Offline [websocket] listening on ws://localhost:3001

My serverless.yml looks like

functions:
  httpHandler:
    handler: graphql.handleHTTP
    events:
      - http:
          path: /
          method: any
          cors: true
  wsHandler:
    handler: graphql.handleWebSocket
    events:
      - websocket:
          route: $connect
      - websocket:
          route: $disconnect
      - websocket:
          route: $default
  eventProcessorHandler:
    handler: graphql.handleEvents
    events:
      - stream:
          enabled: true
          type: dynamodb
          arn:
            Fn::GetAtt: [EventsDynamoDBTable, StreamArn]

If i open GraphQL playground at http://localhost:3000, I can successfully speak with the server and do queries and mutations. In my mutation I am also publishing and I can verify this in the DynamoDB Events table which class DynamoDBEventStore uses.

export const createSomething = async (
	_: any,
	{ foo, bar }: FooBar,
	{
		dataSources,
		pubsub
	}: { dataSources: { baz: BazApi }; pubsub: PubSub }
) => {
	const response = await dataSources.baz.createSomething(
		foo as Foo,
		bar as Bar
	);

	await pubsub.publish(SOMETHING_CREATED, response);

	return response;
};

The problem is if i open GraphQL Playground in two browsers, Run subscription in one browser so that it goes to listening... mode and run mutation in the other browser.

My expectation is that the Connections, Subscriptions and SubscriptionOperations table in Dynamodb should be populated by this time with a record based on one client is on listening mode.

However, that listening mode stays as it is forever. However, I see one record populated in the Events table.

Please let me know if you have more questions. I know I am not able to put properly into words the problem.

from aws-lambda-graphql.

raheelkhan avatar raheelkhan commented on May 13, 2024 1

Hi @michalkvasnicak

Thanks for your reply.

I was trying to dig in the code and found two things that I need to do.

1 - In order to use GraphQL playground that comes with ApolloServer, we need to tell the websocket URL explicitly

const server = new Server({
	connectionManager,
	eventProcessor: new DynamoDBEventProcessor(),
	resolvers,
	subscriptionManager,
	typeDefs,
	dataSources,
	context: {
		pubsub
	},
	playground: {
		subscriptionEndpoint: "ws://localhost:3001"
	}
});

After doing this, If i run subscription via PlayGround, it connects fine.

2 - Second in file DynamoDBConnectionManager.js the this.hydrateConnection method was failing because it was trying to read record from Connections table and returning empty {}. I think its a race condition somehow. I explicitly set the retryCount variable to 5 and it worked until here.

Now my situation is that I can subscribe to the server, my PlayGround is in listening mode, The records are created in Connections, Subscriptions, SubscriptionOperations table which seems all good. However, upon mutation, my subscription client is still on listening mode. If I check Events table, there is a published event present which I published in my mutation function.

Can you see what could potentially be wrong here.?

Thanks alot for helping.

from aws-lambda-graphql.

michalkvasnicak avatar michalkvasnicak commented on May 13, 2024 1

I don't have any experience with localstack but you already know that your stream is not listening to events from dynamodb because that is something that is handled by https://github.com/michalkvasnicak/aws-lambda-graphql/blob/master/packages/chat-example-server/serverless.yml#L11

So you need to find a way how to listen to those changes and call the event handler for event processor. That's not something that is a part of this library because in AWS DynamoDB Stream is responsible for calling your Lambda function. In local environment you need an intermediary mechanism that will connect to a stream and call your handler.

from aws-lambda-graphql.

michalkvasnicak avatar michalkvasnicak commented on May 13, 2024 1

@IslamWahid I added the code you proposed to the server example.

from aws-lambda-graphql.

michalkvasnicak avatar michalkvasnicak commented on May 13, 2024

@raheelkhan could please provide how you test it.

  1. you use playground to connect to this endpoint and listen to subscription (if listening mode runs then this part is ok)
  2. do you fire some action using Mutation to publish events?

I don't understand what effect should be visible on server. Could you please explain it?

from aws-lambda-graphql.

michalkvasnicak avatar michalkvasnicak commented on May 13, 2024

@raheelkhan thank you, could you please provide a schema definition and how the context and pubSub are created?

from aws-lambda-graphql.

raheelkhan avatar raheelkhan commented on May 13, 2024

Sure,

Main

const eventStore = new DynamoDBEventStore({ dynamoDbClient });
const pubsub = new PubSub({ eventStore });
const subscriptionManager = new DynamoDBSubscriptionManager({ dynamoDbClient });
const connectionManager = new DynamoDBConnectionManager({
	apiGatewayManager: process.env.IS_OFFLINE
		? new ApiGatewayManagementApi({ endpoint: "http://localhost:3001" })
		: undefined,
	dynamoDbClient,
	subscriptions: subscriptionManager
});

const server = new Server({
	connectionManager,
	eventProcessor: new DynamoDBEventProcessor(),
	resolvers,
	subscriptionManager,
	typeDefs,
	dataSources,
	context: {
		pubsub
	}
});

export const handleWebSocket = server.createWebSocketHandler();
export const handleHTTP = server.createHttpHandler();
export const handleEvents = server.createEventHandler();

Schema

type WalkinAppointment {
    contactId: Int!
    productId: Int!
    appointmentId: Int!
}
type Subscription {
    somethingCreated: Something!
}

Subscription Resolver

const dynamoDbClient = new DynamoDB.DocumentClient({
	endpoint: process.env.ENDPOINT_DYNAMODB
});

const eventStore = new DynamoDBEventStore({ dynamoDbClient });
const pubsub = new PubSub({ eventStore });

export const resolvers: IResolvers = {
	Query: {
		...
	},
	Mutation: {
		createSomething
	},
	Subscription: {
		walkinAppointmentCreated: {
			subscribe: () => pubsub.subscribe(SOME_THING_CREATED)
		}
	}
};

from aws-lambda-graphql.

raheelkhan avatar raheelkhan commented on May 13, 2024

My Mutation part is working fine, the pubsub.publish() in the mutation function createSomething is also working as expected. Only I am not able to use the websocket feature.

upon start of server it says websocket server is running on ws://localhost:3001. I tried from developers tools var websocket = new WebSocket("ws://localhost:3001"); and it is connected successfully. Which means the API Gateway part is running fine.

from aws-lambda-graphql.

michalkvasnicak avatar michalkvasnicak commented on May 13, 2024

I'm sorry for the late response. Could you please change the subscribe: () => pubsub.subscribe(SOME_THING_CREATED) to subscribe: pubSub.subscribe(SOME_THING_CREATED) ?

The problem is that subscribe has same notation as resolver so when the subscribe is called, it gets root, args, context, info arguments. pubSub.subscribe() returns a function with the same notation so you have 2 ways how to do it:

export const resolvers: IResolvers = {
	Query: {
		/* ... */
	},
	Mutation: {
		/* ... */
	},
	Subscription: {
		walkinAppointmentCreated: {
			subscribe: pubsub.subscribe(SOME_THING_CREATED)
		}
	}
};

or

export const resolvers: IResolvers = {
	Query: {
		/* ... */
	},
	Mutation: {
		/* ... */
	},
	Subscription: {
		walkinAppointmentCreated: {
			subscribe: (...args) => pubsub.subscribe(SOME_THING_CREATED)(...args)
		}
	}
};

If you need to access pubSub from the context, please follow https://github.com/michalkvasnicak/aws-lambda-graphql#16-pass-pubsub-to-resolvers-using-graphql-context

from aws-lambda-graphql.

michalkvasnicak avatar michalkvasnicak commented on May 13, 2024

Only thing that comes to my mind is that setting up subscriptions using pubSub.subscribe() is fragile. It's really easy to mess it up as I explained in the comment above.

Basically you have to define subscribe in your schema correctly.

{
  subscribe: pubSub.subscribe(event);
}

or

{
  subscribe: (...args) => pubSub.subscribe(event)(...args);
}

or with context

{
  subscribe: (root, args, ctx, info) => ctx.pubSub.subscribe(event)(root, args, ctx, info);
}

If you have your subscriptions set up like this, then there shouldn't be a problem.

Other thing could be some problem with serverless-offline.

What version of aws-lambda-graphql are you using? Race condition should be fixed in #68 which was released as 1.0.0-alpha.3

from aws-lambda-graphql.

raheelkhan avatar raheelkhan commented on May 13, 2024

for race condition in my observation hydrateConnection retries only once. Is there any way we can pass this as an argument?

from aws-lambda-graphql.

michalkvasnicak avatar michalkvasnicak commented on May 13, 2024

I don't understand how it can result in a race condition.

  1. When you connect to WebSocket using subscription the $connect route is called and the connection is registered.
  2. Then you fire mutation right? So it's published after the connection is established.

I assume that you test it manually, that means that you should be able to prevent any weird cases. If it doesn't work even when you first subscribe and then fire a mutation after few seconds then it's really weird.

Basically you have a problem when you want to publish event back to subscribers (because you have an event in Events table).

Only thing I have from you is the code in comment:

const dynamoDbClient = new DynamoDB.DocumentClient({
	endpoint: process.env.ENDPOINT_DYNAMODB
});

const eventStore = new DynamoDBEventStore({ dynamoDbClient });
const pubsub = new PubSub({ eventStore });

export const resolvers: IResolvers = {
	Query: {
		...
	},
	Mutation: {
		createSomething
	},
	Subscription: {
		walkinAppointmentCreated: {
			subscribe: () => pubsub.subscribe(SOME_THING_CREATED)
		}
	}
};

Given this code, it's not correct because walkinAppointmentCreated should be defined like this:

const dynamoDbClient = new DynamoDB.DocumentClient({
	endpoint: process.env.ENDPOINT_DYNAMODB
});

const eventStore = new DynamoDBEventStore({ dynamoDbClient });
const pubsub = new PubSub({ eventStore });

export const resolvers: IResolvers = {
	Query: {
		...
	},
	Mutation: {
		createSomething
	},
	Subscription: {
		walkinAppointmentCreated: {
			subscribe: (...args) => pubsub.subscribe(SOME_THING_CREATED)(...args)
		}
	}
};

from aws-lambda-graphql.

raheelkhan avatar raheelkhan commented on May 13, 2024

Sorry, I forgot to mention I am already using the same way you have mentioned. Problem lies somewhere in DynamoDBEventProcessor class. My subscriptions are registered. Only I am not receiving response from server it keeps on listening mode. I am trying to find loophole. I will update you.

Thanks

from aws-lambda-graphql.

michalkvasnicak avatar michalkvasnicak commented on May 13, 2024

@raheelkhan have you tried to change retry count in code of library if it helps?

from aws-lambda-graphql.

raheelkhan avatar raheelkhan commented on May 13, 2024

@michalkvasnicak Yes this is one part of the issue.

In file DynamoDBConnectionManager.js:19 I have set up hard value 5 in the for loop instead of retryCount. This solves my first problem where my subscription are being registered.

from aws-lambda-graphql.

michalkvasnicak avatar michalkvasnicak commented on May 13, 2024

The second problem is that it does not send a data over subscription back to client that has subscribed? This one is weird because the connection already should exist in your table, so there shouldn't be any problem with fetching the connection.

Does event processor even receive events from DynamoDB event stream?

from aws-lambda-graphql.

raheelkhan avatar raheelkhan commented on May 13, 2024

Does event processor even receive events from DynamoDB event stream?

Can you please guide me how can I track that, this is what I am struggling to reach.

from aws-lambda-graphql.

michalkvasnicak avatar michalkvasnicak commented on May 13, 2024

Basically this is the code of event processor:

https://github.com/michalkvasnicak/aws-lambda-graphql/blob/master/packages/aws-lambda-graphql/src/DynamoDBEventProcessor.ts

It should be enough just to check what Records contain. https://github.com/michalkvasnicak/aws-lambda-graphql/blob/master/packages/aws-lambda-graphql/src/DynamoDBEventProcessor.ts#L33

If event processor is being called then you should be able to see events received from DynamoDB.

from aws-lambda-graphql.

michalkvasnicak avatar michalkvasnicak commented on May 13, 2024

If is logged then you can pass eventProcessor with onError handler and log errors that occurred during event processing.

const server = new Server({
	/* ... */
	eventProcessor: new DynamoDBEventProcessor({ onError: e => console.log(e) }),
});

from aws-lambda-graphql.

raheelkhan avatar raheelkhan commented on May 13, 2024

I think eventProcessor is not getting called in my case. Its only working for httpHandler
Screen Shot 2020-02-11 at 5 30 51 PM

I put this line in the DynamoDBEventProcessor::createHandler(server).

const connectionManager = new DynamoDBConnectionManager({
	apiGatewayManager: process.env.IS_OFFLINE
		? new ApiGatewayManagementApi({ endpoint: "http://localhost:3001" })
		: undefined,
	dynamoDbClient,
	subscriptions: subscriptionManager
});

is the apiGatewayManager url matters in this situtation ? I am not using this url anywhere, just followed it from the documentation of this package.

from aws-lambda-graphql.

michalkvasnicak avatar michalkvasnicak commented on May 13, 2024

Ok, that can be maybe some problem with your serverless.yml configuration. Did you compare it with https://github.com/michalkvasnicak/aws-lambda-graphql/blob/master/packages/chat-example-server/serverless.yml?

from aws-lambda-graphql.

raheelkhan avatar raheelkhan commented on May 13, 2024

I compared, I am not using serverless-dynamodb-local, instead I am using localstack which has dynamodb builtin. But other things are same.

from aws-lambda-graphql.

michalkvasnicak avatar michalkvasnicak commented on May 13, 2024

@raheelkhan have you found a solution? It could be nice to have a mention about how to use this with a local stack in README and maybe an example for local stack :)

from aws-lambda-graphql.

raheelkhan avatar raheelkhan commented on May 13, 2024

from aws-lambda-graphql.

michalkvasnicak avatar michalkvasnicak commented on May 13, 2024

Ok thank you very much, I'm closing this for now. If anyone stumbles on this issue and has a solution for LocalStack, feel free to post here.

from aws-lambda-graphql.

IslamWahid avatar IslamWahid commented on May 13, 2024

playground: {
subscriptionEndpoint: "ws://localhost:3001"
}

@michalkvasnicak Can you please update that on the server example?

from aws-lambda-graphql.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.