

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 将 GraphQL 解析器组合在一起 AWS AppSync
<a name="tutorial-combining-graphql-resolvers"></a>

**注意**  
我们现在主要支持 APPSYNC\$1JS 运行时系统及其文档。请考虑使用 APPSYNC\$1JS 运行时系统和[此处](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)的指南。

GraphQL 架构中的解析器和字段具有 1:1 的关系，具有很高的灵活性。由于数据来源是独立于架构在解析器上配置的，因此您可以通过不同的数据来源解析和操控 GraphQL 类型，同时在架构上混合和匹配数据来源以最好地满足您的需要。

以下示例场景说明了如何在架构中混合使用和匹配数据来源。在开始之前，我们建议您熟悉如何为 AWS Lambda Amazon DynamoDB和亚马逊服务设置数据源 OpenSearch 和解析器，如前面的教程所述。

## 示例架构
<a name="example-schema"></a>

以下架构具有一个名为 `Post` 的类型，它定义了 3 个 `Query` 操作和 3 个 `Mutation` 操作：

```
type Post {
    id: ID!
    author: String!
    title: String
    content: String
    url: String
    ups: Int
    downs: Int
    version: Int!
}

type Query {
    allPost: [Post]
    getPost(id: ID!): Post
    searchPosts: [Post]
}

type Mutation {
    addPost(
        id: ID!,
        author: String!,
        title: String,
        content: String,
        url: String
    ): Post
    updatePost(
        id: ID!,
        author: String!,
        title: String,
        content: String,
        url: String,
        ups: Int!,
        downs: Int!,
        expectedVersion: Int!
    ): Post
    deletePost(id: ID!): Post
}
```

在本示例中，您共有 6 个可附加的解析器。一种可能的方法是，让所有这些解析器来自名为 `Posts` 的 Amazon DynamoDB 表，其中 `AllPosts` 运行扫描，`searchPosts` 运行查询，如 [DynamoDB 解析器映射模板参考](resolver-mapping-template-reference-dynamodb.md#aws-appsync-resolver-mapping-template-reference-dynamodb)中所述。但是，还有其他方法可以满足您的业务需求，例如让这些 GraphQL 查询从 Lambda 或服务中解析。 OpenSearch 

## 通过解析程序更改数据
<a name="alter-data-through-resolvers"></a>

您可能需要将结果从数据库（例如 DynamoDB 或 Amazon Aurora）返回到客户端，并更改某些属性。这可能是由于数据类型的格式设置所致（例如，客户端上的时间戳差异），或者是为了处理向后兼容性问题。出于说明目的，在以下示例中，每次调用 GraphQL 解析器时， AWS Lambda 函数为其分配随机数以处理博客文章的点赞和差评操作：

```
'use strict';
const doc = require('dynamodb-doc');
const dynamo = new doc.DynamoDB();

exports.handler = (event, context, callback) => {
    const payload = {
        TableName: 'Posts',
        Limit: 50,
        Select: 'ALL_ATTRIBUTES',
    };

    dynamo.scan(payload, (err, data) => {
        const result = { data: data.Items.map(item =>{
            item.ups = parseInt(Math.random() * (50 - 10) + 10, 10);
            item.downs = parseInt(Math.random() * (20 - 0) + 0, 10);
            return item;
        }) };
        callback(err, result.data);
    });
};
```

这是一个完全有效的 Lambda 函数，可以附加到 GraphQL 架构中的 `AllPosts` 字段，以便返回所有结果的任何查询都可以对顶/踩操作获得随机数字。

## DynamoDB 和服务 OpenSearch
<a name="ddb-and-es"></a>

对于某些应用程序，您可以对 DynamoDB 执行变更或简单查找查询，并让后台进程将文档传输到服务。 OpenSearch 然后，您只需将`searchPosts`解析器附加到 OpenSearch 服务数据源，然后使用 GraphQL 查询返回搜索结果（来自源自 DynamoDB 的数据）。在应用程序中添加高级搜索操作（例如关键字、模糊字词匹配，甚至地理空间查找）时，这可能是非常强大的。可以通过 ETL 流程完成从 DynamoDB 传输数据的过程，也可以使用 Lambda 从 DynamoDB 进行流式传输。您可以使用 AWS 账户中位于美国西部 2（俄勒冈）地区的以下 AWS CloudFormation 堆栈启动一个完整的示例：

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/multipledatasource/appsyncesdbstream.yml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/multipledatasource/appsyncesdbstream.yml) 

该示例中的架构允许您使用 DynamoDB 解析器添加文章，如下所示：

```
mutation add {
    putPost(author:"Nadia"
        title:"My first post"
        content:"This is some test content"
        url:"https://aws.amazon.com/appsync/"
    ){
        id
        title
    }
}
```

这会将数据写入 DynamoDB，DynamoDB 然后通过 Lambda 将数据流式传输到 OpenSearch 亚马逊服务，您可以按不同的字段搜索所有帖子。例如，由于数据存储在 Amazon S OpenSearch ervice 中，因此您可以使用自由格式的文本（即使是空格）搜索作者字段或内容字段，如下所示：

```
query searchName{
    searchAuthor(name:"   Nadia   "){
        id
        title
        content
    }
}

query searchContent{
    searchContent(text:"test"){
        id
        title
        content
    }
}
```

由于数据直接写入到 DynamoDB 中，因此，您仍然可以使用 `allPosts{...}` 和 `singlePost{...}` 查询对表执行高效的列表或项目查找操作。该堆栈将以下示例代码用于 DynamoDB 流：

 **注意**：此代码仅用于举例说明。

```
var AWS = require('aws-sdk');
var path = require('path');
var stream = require('stream');

var esDomain = {
    endpoint: 'https://opensearch-domain-name.REGION.es.amazonaws.com',
    region: 'REGION',
    index: 'id',
    doctype: 'post'
};

var endpoint = new AWS.Endpoint(esDomain.endpoint)
var creds = new AWS.EnvironmentCredentials('AWS');

function postDocumentToES(doc, context) {
    var req = new AWS.HttpRequest(endpoint);

    req.method = 'POST';
    req.path = '/_bulk';
    req.region = esDomain.region;
    req.body = doc;
    req.headers['presigned-expires'] = false;
    req.headers['Host'] = endpoint.host;

    // Sign the request (Sigv4)
    var signer = new AWS.Signers.V4(req, 'es');
    signer.addAuthorization(creds, new Date());

    // Post document to ES
    var send = new AWS.NodeHttpClient();
    send.handleRequest(req, null, function (httpResp) {
        var body = '';
        httpResp.on('data', function (chunk) {
            body += chunk;
        });
        httpResp.on('end', function (chunk) {
            console.log('Successful', body);
            context.succeed();
        });
    }, function (err) {
        console.log('Error: ' + err);
        context.fail();
    });
}

exports.handler = (event, context, callback) => {
    console.log("event => " + JSON.stringify(event));
    var posts = '';

    for (var i = 0; i < event.Records.length; i++) {
        var eventName = event.Records[i].eventName;
        var actionType = '';
        var image;
        var noDoc = false;
        switch (eventName) {
            case 'INSERT':
                actionType = 'create';
                image = event.Records[i].dynamodb.NewImage;
                break;
            case 'MODIFY':
                actionType = 'update';
                image = event.Records[i].dynamodb.NewImage;
                break;
            case 'REMOVE':
            actionType = 'delete';
                image = event.Records[i].dynamodb.OldImage;
                noDoc = true;
                break;
        }

        if (typeof image !== "undefined") {
            var postData = {};
            for (var key in image) {
                if (image.hasOwnProperty(key)) {
                    if (key === 'postId') {
                        postData['id'] = image[key].S;
                    } else {
                        var val = image[key];
                        if (val.hasOwnProperty('S')) {
                            postData[key] = val.S;
                        } else if (val.hasOwnProperty('N')) {
                            postData[key] = val.N;
                        }
                    }
                }
            }

            var action = {};
            action[actionType] = {};
            action[actionType]._index = 'id';
            action[actionType]._type = 'post';
            action[actionType]._id = postData['id'];
            posts += [
                JSON.stringify(action),
            ].concat(noDoc?[]:[JSON.stringify(postData)]).join('\n') + '\n';
        }
    }
    console.log('posts:',posts);
    postDocumentToES(posts, context);
};
```

然后，您可以使用 DynamoDB 流将其附加到主键`id`为的 DynamoDB 表，并且对 DynamoDB 源的任何更改都将流式传输到您的服务域中。 OpenSearch 有关配置上述功能的更多信息，请参阅 [DynamoDB 流文档](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.Lambda.html)。