- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
首先,我对在 Stack Overflow 上实际发帖还比较陌生,但我当然会尽最大努力在此处提供所有相关信息,并在找到解决方案后分享解决方案,因为我可以想象更多人可能会遇到这个问题。
因此,我们从一个具有多个小型微服务作为后端的系统开始,我们发现 Apollo 服务器能够从 graphql 端点检索模式并将它们拼接在一起,这样我们就有了一个很好的入口点。我们已经做到了,但 apollo 服务器对整体架构没有任何帮助。那是我们发现 NestJS 的时候,因为我们在前端使用了 angular,而 NestJS 与它非常相似,看起来非常适合。
但我们遇到的问题是我们似乎无法使以下功能正常工作:- 我想要一个模块,其中包含一个可以提供多个端点的服务(uri 到微服务)- 有了给定的点,服务应该从这些端点检索 graphQL 模式并将它们放入 RemoteExecutableSchemas,然后合并它们。- 合并它们并使用(远程)链接信息制作 1 个大模式之后,以便 graphQL 知道从哪里获取数据。- 发生这种情况后,我们想添加一些拼接,以便所有关系都存在(但这不是我的问题所在)
我一直在浏览官方文档 ( https://docs.nestjs.com/graphql/quick-start ) 以及它们的示例 ( https://github.com/nestjs/nest/tree/master/sample/12-graphql-apollo ),当然还检查了 github 项目 ( https://github.com/nestjs/graphql ) 并一直在这个 repo 中查看以查看代码在后台执行。
我们已经尝试了几种方法来动态获取它们,但无法在实例化之前将模式放入 GraphQLModule 中。然后我们认为让服务从端点检索 graphqlSchema 并使用实际工作的 printSchema(schema) 将其写入文件可能是可以接受的,但后来我丢失了有效的链接信息,使其成为本地模式而不是远程模式.现在我们提出了以下内容,但再次陷入困境。
让我们从我的 package.json 中的一小段开始,这样人们就知道版本了:)
"dependencies": {
"@nestjs/common": "^5.4.0",
"@nestjs/core": "^5.4.0",
"@nestjs/graphql": "^5.5.1",
"apollo-link-http": "^1.5.9",
"apollo-server-express": "^2.3.2",
"graphql": "^14.1.1",
"reflect-metadata": "^0.1.12",
"rimraf": "^2.6.2",
"rxjs": "^6.2.2",
"typescript": "^3.0.1"
},
"devDependencies": {
"@nestjs/testing": "^5.1.0",
"@types/express": "^4.16.0",
"@types/jest": "^23.3.1",
"@types/node": "^10.7.1",
"@types/supertest": "^2.0.5",
"jest": "^23.5.0",
"nodemon": "^1.18.3",
"prettier": "^1.14.2",
"supertest": "^3.1.0",
"ts-jest": "^23.1.3",
"ts-loader": "^4.4.2",
"ts-node": "^7.0.1",
"tsconfig-paths": "^3.5.0",
"tslint": "5.11.0"
},
所以,目前我有一个模式处理程序模块,如下所示:
@Module({
imports: [GraphQLModule.forRootAsync({
useClass: GqlConfigService
})],
controllers: [SchemaHandlerController],
providers: [SchemaFetcherService, SchemaSticherService, GqlConfigService]
})
export class SchemaHandlerModule {
}
所以在这里我们导入 GraphQLModule 并让它使用 gql-config 服务来处理给它 GraphQLModuleOptions。
gql-config 服务如下所示:
@Injectable()
export class GqlConfigService implements GqlOptionsFactory {
async createGqlOptions(): Promise<GqlModuleOptions> {
try{
const countrySchema = this.createCountrySchema();
return {
typeDefs: [countrySchema]
};
} catch(err) {
console.log(err);
return {};
}
}
所以我正在异步创建 GqlModuleOptions 并等待结果。 createCountrySchema 函数如下所示:
public async createCountrySchema() : GraphQLSchema{
const uri = 'https://countries.trevorblades.com/Graphql';
try {
const link = new HttpLink({
uri: uri,
fetch
});
const remoteSchema = await introspectSchema(link);
return makeRemoteExecutableSchema({
schema: remoteSchema,
link
});
} catch (err) {
console.log('ERROR: exception when trying to connect to ' + uri + ' Error Message: ' + err);
}
};
为了 POC,我只获得了一个简单的公共(public) graphQL API 作为端点。此函数返回一个 GraphQLSchema 对象,然后我想将其添加(以某种方式)到 GqlOptions 并使其在 Playground 上可见。 我们还尝试让 createCountrySchema 返回一个 Promise 并在调用 createGqlOptions 中的函数时等待它,但这似乎没有什么不同。
我们得到的实际错误看起来像这样:
[Nest] 83 - 2/1/2019, 2:10:57 PM [RoutesResolver] SchemaHandlerController {/schema-handler}: +1ms
apollo_1 | (node:83) UnhandledPromiseRejectionWarning: Syntax Error: Unexpected [
apollo_1 |
apollo_1 | GraphQL request (2:9)
apollo_1 | 1:
apollo_1 | 2: [object Promise]
apollo_1 | ^
apollo_1 | 3:
apollo_1 |
apollo_1 | at syntaxError (/opt/node_modules/graphql/error/syntaxError.js:24:10)
apollo_1 | at unexpected (/opt/node_modules/graphql/language/parser.js:1483:33)
apollo_1 | at parseDefinition (/opt/node_modules/graphql/language/parser.js:155:9)
apollo_1 | at many (/opt/node_modules/graphql/language/parser.js:1513:16)
apollo_1 | at parseDocument (/opt/node_modules/graphql/language/parser.js:115:18)
apollo_1 | at parse (/opt/node_modules/graphql/language/parser.js:50:10)
apollo_1 | at parseDocument (/opt/node_modules/graphql-tag/src/index.js:129:16)
apollo_1 | at Object.gql (/opt/node_modules/graphql-tag/src/index.js:170:10)
apollo_1 | at GraphQLFactory.<anonymous> (/opt/node_modules/@nestjs/graphql/dist/graphql.factory.js:48:55)
apollo_1 | at Generator.next (<anonymous>)
apollo_1 | (node:83) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
apollo_1 | (node:83) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
我认为我很接近这种方法,但我不太确定。我收到的错误指出所有 Promises 都必须用 try/catch 处理,这样我们就不会得到未处理的 Promise,我相信我在任何地方都这样做,所以我不明白这个错误是从哪里来的......
如果有人有任何指示、解决方案或建议,我将非常非常高兴。一个多星期以来,我一直在努力让我们想要的功能安装在 nestjs 中,并且已经看到了大量的示例、片段和讨论,但我找不到一个示例来缝合远程模式并将它们交还给 nestjs。
如果对此有任何评论,我将不胜感激,并致以亲切的问候,蒂尔德
最佳答案
我已经使用转换方法解决了模式拼接问题。
看src/graphql.config/graphql.config.service.ts
here my code
link for the test
import { Injectable } from '@nestjs/common';
import { GqlOptionsFactory, GqlModuleOptions } from '@nestjs/graphql';
import * as ws from 'ws';
import {
makeRemoteExecutableSchema,
mergeSchemas,
introspectSchema
} from 'graphql-tools';
import { HttpLink } from 'apollo-link-http';
import nodeFetch from 'node-fetch';
import { split, from, NextLink, Observable, FetchResult, Operation } from 'apollo-link';
import { getMainDefinition } from 'apollo-utilities';
import { OperationTypeNode, buildSchema as buildSchemaGraphql, GraphQLSchema, printSchema } from 'graphql';
import { setContext } from 'apollo-link-context';
import { SubscriptionClient, ConnectionContext } from 'subscriptions-transport-ws';
import * as moment from 'moment';
import { extend } from 'lodash';
import { ConfigService } from '../config';
declare const module: any;
interface IDefinitionsParams {
operation?: OperationTypeNode;
kind: 'OperationDefinition' | 'FragmentDefinition';
}
interface IContext {
graphqlContext: {
subscriptionClient: SubscriptionClient,
};
}
@Injectable()
export class GqlConfigService implements GqlOptionsFactory {
private remoteLink: string = 'https://countries.trevorblades.com';
constructor(
private readonly config: ConfigService
) {}
async createGqlOptions(): Promise<GqlModuleOptions> {
const remoteExecutableSchema = await this.createRemoteSchema();
return {
autoSchemaFile: 'schema.gql',
transformSchema: async (schema: GraphQLSchema) => {
return mergeSchemas({
schemas: [
schema,
remoteExecutableSchema
]
});
},
debug: true,
playground: {
env: this.config.environment,
endpoint: '/graphql',
subscriptionEndpoint: '/subscriptions',
settings: {
'general.betaUpdates': false,
'editor.theme': 'dark' as any,
'editor.reuseHeaders': true,
'tracing.hideTracingResponse': true,
'editor.fontSize': 14,
// tslint:disable-next-line:quotemark
'editor.fontFamily': "'Source Code Pro', 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace",
'request.credentials': 'include',
},
},
tracing: true,
installSubscriptionHandlers: true,
introspection: true,
subscriptions: {
path: '/subscriptions',
keepAlive: 10000,
onConnect: async (connectionParams, webSocket: any, context) => {
const subscriptionClient = new SubscriptionClient(this.config.get('HASURA_WS_URI'), {
connectionParams: {
...connectionParams,
...context.request.headers
},
reconnect: true,
lazy: true,
}, ws);
return {
subscriptionClient,
};
},
async onDisconnect(webSocket, context: ConnectionContext) {
const { subscriptionClient } = await context.initPromise;
if (subscriptionClient) {
subscriptionClient.close();
}
},
},
context(context) {
const contextModified: any = {
userRole: 'anonymous',
currentUTCTime: moment().utc().format()
};
if (context && context.connection && context.connection.context) {
contextModified.subscriptionClient = context.connection.context.subscriptionClient;
}
return contextModified;
},
};
}
private wsLink(operation: Operation, forward?: NextLink): Observable<FetchResult> | null {
const context = operation.getContext();
const { graphqlContext: { subscriptionClient } }: any = context;
return subscriptionClient.request(operation);
}
private async createRemoteSchema(): Promise<GraphQLSchema> {
const httpLink = new HttpLink({
uri: this.remoteLink,
fetch: nodeFetch as any,
});
const remoteIntrospectedSchema = await introspectSchema(httpLink);
const remoteSchema = printSchema(remoteIntrospectedSchema);
const link = split(
({ query }) => {
const { kind, operation }: IDefinitionsParams = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
this.wsLink,
httpLink,
);
const contextLink = setContext((request, prevContext) => {
extend(prevContext.headers, {
'X-hasura-Role': prevContext.graphqlContext.userRole,
'X-Hasura-Utc-Time': prevContext.graphqlContext.currentUTCTime,
});
return prevContext;
});
const buildedHasuraSchema = buildSchemaGraphql(remoteSchema);
const remoteExecutableSchema = makeRemoteExecutableSchema({
link: from([contextLink, link]),
schema: buildedHasuraSchema,
});
return remoteExecutableSchema;
}
}
关于typescript - 如何使用 Nestjs 和 apollo 服务器拼接多个远程模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54481645/
我在开发应用程序时将配置保存在 .env 文件中。 这是我的 app.module.ts: @Module({ imports: [ ConfigModule.forRoot({ isGl
我正在使用我购买的 akveo 后端包,虽然在开发模式下一切似乎都在生产中运行良好,但出现了以下错误,但我对 nestjs 本身并不熟悉。 有谁知道这里发生了什么? node_modules/@nes
我刚刚开始使用 Nestjs我想知道如何使用路由前缀或通过 Express Router 实例对我的 API 进行版本控制? 理想情况下,我希望通过以下方式访问端点: /v1 /v2 等等,这样我就可
我想了解将服务提供商注入(inject) NestJS Controller 的目的是什么?这里的文档在这里解释了如何使用它们,这不是这里的问题:https://docs.nestjs.com/pro
我正在使用@goevelup/nestjs-rabbitmq库构建一个NestJS应用程序,以便将消息发布到rabbitmq交易所。。我正在AppModule中导入和配置RabbitMQ模块(这部分似
我正在使用@goevelup/nestjs-rabbitmq库构建一个NestJS应用程序,以便将消息发布到rabbitmq交易所。。我正在AppModule中导入和配置RabbitMQ模块(这部分似
我正在制作 @nestjs/swagger生成api文档。但是如何为经过身份验证的路由生成文档? 嵌套版本 λ nest i NodeJS Version : v10.16.0 [Nest Infor
NestJs 允许导出模块和提供者。它们有什么区别? 例子: // Reusable module @Module({ providers: [ServiceA], exports: [Service
我有一个返回字符串的 Controller 处理程序。 // Controller.ts import { Controller, Get, UseInterceptors } from '@nest
在 NestJS API 上,我想在实体中使用模块服务,以使用非数据库属性填充该实体。 在我的例子中,我想获得我正在检索的类别的文章数量。 @Entity({ name: 'categories' }
我正在设置一个新的 NestJS 应用程序,我刚刚添加了类验证器以验证 Controller 输入,但它似乎被完全忽略了。这是 DTO: import {IsString} from 'class-v
我想要一些环境,比如说development , production , test .这些环境应该是独立的,并使用它们自己的配置参数集,例如对于 DB、SERVER_PORT、USER 等。 它们不
我觉得 this thread 和 this thread 的组合是我需要实现的,我无法将它们绘制在一起。 我有一个包含 enum 的 DTO。 使用 Postman,我发送 PurchasableT
我是 NestJs 的新手,我创建了一个回退异常过滤器,现在我想知道如何使用它。换言之,如何将其导入我的应用程序? 这是我的后备异常过滤器: @Catch(HttpException) export
我在oracle数据库中有存储过程,我想在NestJs中调用它。 如何在 NestJs 中调用存储过程? 这是我的存储过程 PROCEDURE pipeline_critical (
我想在 nestjs 的验证中使用正则表达式。 例如: 正则表达式 pagePattern = '[a-z0-9\-]+'; 方法 @Get('/:article') getIndex(
我想将配置字符串传递给管道,但也想注入(inject)服务。 NesJs 文档描述了如何相互独立而不是一起执行这两项操作。举个例子: 管道.ts @Injectable() export class
我正在尝试对具有来自 nestjs 护照模块的 AuthGuard 的路由进行端到端测试,但我真的不知道如何处理它。当我运行测试时,它说: [ExceptionHandler] Unknown aut
我正在编写一个 NestJS 应用程序。一些端点支持排序,例如http://127.0.0.1:3000/api/v1/members?sort=-id&take=100 这意味着按 id 降序排序。
我想在 nestjs 的验证中使用正则表达式。 例如: 正则表达式 pagePattern = '[a-z0-9\-]+'; 方法 @Get('/:article') getIndex(
我是一名优秀的程序员,十分优秀!