Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Another "Before and After request interceptor" issue #631

Closed
ElMatella opened this issue Feb 27, 2020 · 4 comments
Closed

Another "Before and After request interceptor" issue #631

ElMatella opened this issue Feb 27, 2020 · 4 comments

Comments

@ElMatella
Copy link

ElMatella commented Feb 27, 2020

I am having problems trying to use a single database connection for an entire graphql request.

I see some issues, open and closed that might relate to my problem:

What I would like to do is open a connection when a request start, and close the connection when the request stops. I tried to use an interceptor:

@Injectable({
  scope: Scope.REQUEST
})
export class ConnectionInterceptor implements NestInterceptor {
  // Connection is a request scoped provider that should be initiated somewhere to work in the providers. It also needs to be destroyed at the end of the request.
  private readonly connection: Connection

  constructor (connection: Connection) {
    this.connection = connection
  }

  async intercept(context: ExecutionContext, next: CallHandler<any>): Promise<Observable<any>> {
    console.log('Init Connection...')
    await this.connection.init()
    return next.handle().pipe(tap(async () => {
      console.log('Release Connection...')
      await this.connection.release()
    }))
  }
}

And the following resolver:

@Resolver('Product')
@UseInterceptors(ConnectionInterceptor)
export class ProductsResolver {
  // DataRepository is also a request scoped provider that injects the "Connection" provider.
  private readonly dataRepository: DataRepository

  constructor (
    dataRepository: DataRepository
  ) {
    this.dataRepository = dataRepository
  }

  @Query()
  async product (@Args('id') id: string) {
    console.log('1')
    const response = await this.dataRepository.loadProduct(id)
    console.log('2')
    return response
  }

  @ResolveProperty('division')
  async division (@Parent() product: DatabaseProduct) {
    console.log('3')
    const response = await this.dataRepository.loadDivision(product.division_id)
    console.log('4')
    return response
  }
}

However, I'm getting the following logs, in that order:

Init Connection...
1
2
Release Connection...
3

And 4 is not logged because the connection is already closed and loadDivision needs the connection provider to be in an activated state so it throws an error.

Is their any way to launch code before the request (like it's actually doing here) and after all the resolverProperties have been resolved?

I don't know how you handle the Interceptor right now. Can't you make use of the Apollo plugins feature? And use the requestDidStart and willSendResponse hooks?

If it is not possible, how do you make use of a raw mysql connection in your apps? Do you have a global connection pool for the entire app? If this is the case, can you still handle transactions in your mutations?

EDIT: I managed to make it work with a really dirty hack, I don't know if I'd rather keep it that way or stop software development for the rest of my life:

  1. Just open the connection into the interceptor and attach the connection to the gql context:
async intercept(context: ExecutionContext, next: CallHandler<any>): Promise<Observable<any>> {
    console.log('Init Connection...')
    const ctx = GqlExecutionContext.create(context);
    ctx.getContext().connection = this.connection
    await this.connection.init()
    return next.handle()
}
  1. Release the connection into a custom apollo plugin:
  imports: [
    GraphQLModule.forRoot({
      typePaths: ['./**/*.graphql'],
      definitions: {
        path: join(process.cwd(), 'src/graphql.ts'),
      },
      plugins: [
        {
          requestDidStart() {
            return {
              async willSendResponse (requestContext) {
                if (requestContext.context.connection) {
                  console.log('releasing connection..')
                  await requestContext.context.connection.release()
                }
              }
            }
          }
        }
      ]
    }),
@kamilmysliwiec
Copy link
Member

Duplicate of #320

@kamilmysliwiec kamilmysliwiec marked this as a duplicate of #320 Mar 2, 2020
@ElMatella
Copy link
Author

ElMatella commented Mar 5, 2020

I'm sorry, I did not want to waste your time...

Do you know if there would be a "nestjs" way to write an Apollo Plugin? Like writing a class supporting dependency injection and triggering methods on Apollo Hooks?

Something like:

@ApolloPlugin()
export class SomeNestApolloPlugin {

  private readonly someRequestScopedProvider: SomeRequestScopedProvider

  constructor (someRequestScopedProvider: SomeRequestScopedProvider) {
    this.someRequestScopedProvider = someRequestScopedProvider
  }
  
  requestDidStart () {
    // Do Something
  }

  parsingDidStart () {
    // Do Something
  }

  validationDidStart () {
    // Do Something
  }

  didResolveOperation () {
    // Do Something
  }

  responseForOperation () {
    // Do Something
  }

  executionDidStart () {
    // Do Something
  }

  didEncounterErrors () {
    // Do Something
  }

  willSendResponse () {
    // Do Something
  }
}

I understand from your response in #320 that using already existing nest features is not possible, and that something like this would be useful for people using this GraphQL package only...

However, I can see some useful usecases such as the one I listed in the issue (database connection per request) and error handling and transforming.

I understand that you must have a lot of work and I would love to contribute but I'm not sure that I have the required level to make this kind of proposal.

Anyways, I'm not sure if this is something that makes sense, but would it be possible to access the request injector (the one that contains the request scoped providers) from the apollo context? This would help write raw Apollo Plugins at least.

Thank you again for everything you do and have a nice evening

https://www.apollographql.com/docs/apollo-server/integrations/plugins/

@kamilmysliwiec
Copy link
Member

Plugins will be supported in the next major release :) (once this PR is merged #634)

@lock
Copy link

lock bot commented Jun 24, 2020

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked as resolved and limited conversation to collaborators Jun 24, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants