aws / aws-appsync-community

The AWS AppSync community
https://aws.amazon.com/appsync
Apache License 2.0
507 stars 32 forks source link

RFC: JavaScript Resolvers in AWS AppSync #147

Closed ahmadizm closed 1 year ago

ahmadizm commented 3 years ago

GraphQL has a built-in compute or runtime component where developers can customize their own business logic directly at the API layer. These components are called Resolvers, and they provide the logical glue between the data defined in the GraphQL schema and the data in the actual data sources. Using resolvers you can map an operation or even a single field of a type defined in the schema with a specific data source, which allows to retrieve data for different fields in different data sources with a single API call. They are called “Resolvers” because they are built-in functions in GraphQL that “resolve” types or fields defined in the GraphQL schema with the data in the data sources.

AppSync currently leverages VTL or Apache Velocity Templates to provide a lean and fast compute runtime layer to resolve GraphQL queries or fields. It uses VTL internally to translate GraphQL requests from clients into a request to a data source as well as translate back the response from the data source to clients.

However, if you’re not familiar with VTL you need to learn a new language to take full advantage of these benefits, which can potentially delay the implementation of a GraphQL project for your business. While there are toolchains such as the Amplify CLI (https://docs.amplify.aws/cli/) and the GraphQL Transformer (https://docs.amplify.aws/cli/graphql-transformer/overview) that can automatically generate VTL for AppSync APIs, customers have told us that sometimes they just want to write their own resolver logic in a language they are familiar with and the preferred runtime to do so is JavaScript.

As an alternative to VTL, we're evaluating adding support for JavaScript as a runtime for AppSync resolvers. Developers will be able to leverage all native JavaScript constructs (i.e. switches, maps, global libraries such as JSON and Math, etc) in a familiar programming model based on the latest ECMAScript specification. Ideal for simple to complex business logic where no external modules are required. Just like VTL all JavaScript Resolvers code is hosted, executed and managed internally by AppSync. Customers don’t have to manage their GraphQL API resolvers business logic in other AWS services.

We propose JavaScript Resolvers are stateless and don’t have direct internet/network access to data sources, network access is handled by the AppSync service. For instance, in order to access an external API the resolver needs to be setup as an HTTP Resolver with JavaScript. AppSync executes the business logic defined in the resolver then sends the transformed request to the data source and the data source only (DB or external API). There's no access to the local file system where the Resolver is executed either. Global objects such as JSON and Math are pre-loaded and available, async/await is supported however window() and API's such as fetch and XMLHttpRequest are not.

All the utilities currently provided by VTL ($util - https://docs.aws.amazon.com/appsync/latest/devguide/resolver-util-reference.html) will be available for JavaScript Resolvers unless there’s a related utility already available natively on JavaScript (i.e. JSON.stringify, JSON.parse). Just like JSON and Math, “util” is also a global object built-in to the resolvers and automatically available. Similar to VTL, all data sources information, including connection strings, endpoints, tables and permissions, are all baked into the resolver and managed by AppSync to securely connect to the data sources.

It’s not possible to import external Node.js modules in order to maintain a lean and optimized AppSync runtime layer specifically for GraphQL workloads. If developers want to import modules or do anything more complex, the recommended approach is to use Direct Lambda Resolvers.

Please comment on this thread if you have some thoughts or suggestions on this feature or if you think we’re missing any story points which you would love to see as a part of this feature.

Sample None Resolver:

function handleRequest(context) {

    if (!context.arguments.title
        || !context.arguments.author) {
        const earlyReturnData = {
            "error": "Arguments title and author are required."
        };

        // util is a built-in global object 
        // The following line returns data immediately, bypassing handleRespnse
        // if provided.
        util.returnEarly(earlyReturnData);

        // Alternatively, we can call util.error with our message and data
        // util.error("Error message here", "Error type here", earlyReturnData);
    }

    const autoId = util.autoId(); 

    return {
        version: "2018-05-29",
        payload: {
            id: autoId,
            title:  context.arguments.title,
            author:  context.arguments.author,
            content:  justARandomString(36)
        }
    };
}

// handleResponse function is optional
// Here, we do not need to postprocess the result, so we just omit that function.
// If handleResponse function is not specified, context.result will be returned.

// Helper function
function justARandomString(length) {
    var result = "";
    for(var i=0; i < length; i++){
        var r = Math.random()*16 | 0;
        result += r.toString(16);
    }
    return result;
}

Sample DynamoDB Resolver:

function handleRequest(context) {
    var requestData = {
        "version": "2018-05-29",
        "operation": "Query",
        "query": {
            "expression": "#author = :authorId AND postedAt > :postedAfter",
            "expressionNames": {
                "#author": "authorId"
            },
            "expressionValues": {
                ":authorId": {
                    "S": context.arguments.authorId
                },
                ":postedAfter": {
                    "S": context.arguments.postedAfter
                }
            }
        },

        "index": "postedAtIdx",
        "select" : "ALL_PROJECTED_ATTRIBUTES",
        "consistentRead": true
    }

    if (context.arguments.filter) {
        requestData.filter = {
            "expression": "begins_with(#postId, :filter)",
            "expressionNames": {
                "#postId": "postId"
            },
            "expressionValues": {
                ":filter": {
                    "S": context.arguments.filter
                }
            }
        };
    }

    return requestData;
}

// while handleRequest() is mandatory, 
// handleResponse() is optional. If not present, it's a passthrough
function handleResponse(context) { 

    var result = [];

    if (context.result.items) {
        context.result.items.forEach(function(item) {
            result.push(getIdAndAuthor(item));
        })
    }

    return result;
}

// Helper function
function getIdAndAuthor(item) {
    return {
        "id": item.postId,
        "author": item.authorId
        // ignoring other fields of item
    }
}
markymc commented 3 years ago

Pity there’s no news on this yet. I’m about to start a project and lack of JS support for resolvers is likely to make me move to another solution.

stojanovic commented 3 years ago

@markymc what other solution do you want to use? If you still stay with AWS and keep the app serverless, you'll need to use AWS Lambda with API Gateway. You can still use AWS Lambda with AppSync. With VTL, or these potential JavaScript resolvers, you can have lower latency and probably a bit less moving parts, but they are slightly harder to test.

I am looking forward to new JavaScript resolvers, too. But I am just curious to learn what's your alternative? Mostly because of the AppSync book we are working on.

alan-cooney commented 3 years ago

@stojanovic might be worth checking out https://www.npmjs.com/package/appsync-template-tester if you haven't already

stojanovic commented 3 years ago

@alan-cooney Interesting, thanks. I use the AppSync Simulator package from AWS Amplify, it seems that it has more utilities, but some are still not implemented. It's good enough for most of the unit tests we have.

We'll probably publish the testing chapter of our book as an article soon, as that's one of the biggest pain points for AppSync.

There's also an excellent website to help you render to try rendering templates with different arguments: https://www.graphboss.com.

alan-cooney commented 3 years ago

Ah super interesting thanks - I'll give it a try

markymc commented 3 years ago

@markymc what other solution do you want to use? If you still stay with AWS and keep the app serverless, you'll need to use AWS Lambda with API Gateway. You can still use AWS Lambda with AppSync. With VTL, or these potential JavaScript resolvers, you can have lower latency and probably a bit less moving parts, but they are slightly harder to test.

I am looking forward to new JavaScript resolvers, too. But I am just curious to learn what's your alternative? Mostly because of the AppSync book we are working on.

@stojanovic Good question. I'm considering a few options for my GQL backend (to work with my react-native-web JS frontend, I'm pretty experienced with GQL in general, so I don't need to learn about that), including:

  1. Ruby on Rails on Heroku (with graphql-ruby, postgres, Pusher etc) - which is a very known quantity to me (over a decade of experience), but can get expensive fairly quickly and has a few moving parts, although Heroku pipelines are very nice.
  2. A pure JS solution, probably with something like Apollo Server with Prisma (and perhaps Nexus?), not sure where I'd host yet - I've used Apollo Client extensively, but not the server. I like what I see so far about Prisma, although the idea of managing my own servers isn't attractive.
  3. Something more "serverless", which is still a new field to me in practice, but seems like something I should be seriously considering - I like the idea of Amplify, but it feels a bit too "magic" from my early research. This is probably because there seems to be a lack of documentation (either from Amazon or the wider community) showing how to create a medium complexity GQL backend from scratch to production and staging setups (with all the usual stuff like testing, backups, migrations, etc), which is really problematic for me. Perhaps I'm just not looking in the right place? It certainly doesn't help that I basically have no experience with DynamoDB. Having to learn VTL isn't ideal, as I'd like to keep a pure JS setup front and back if possible.
  4. Other platforms, like 8base, Firebase, etc. I just don't really like the idea of managing things via web GUI instead of writing code to define everything. Perhaps I'm missing a detail.

I had a look at the TOC for your book... looks like exactly what I need!!! I don't suppose you need a test user in the near future? I'd be happy to provide feedback!

stojanovic commented 3 years ago

@markymc yes, these options seem reasonable. However, from my experience so far, most of them are more complicated and expensive than learning a few VTL tricks or writing direct Lambda integrations for the AppSync back end.

We run our product 100% on serverless using AppSync, and so far, three full stack developers can accomplish a lot with a low infrastructure cost(less than 1% of our MRR for all environments). VTL seems tricky, but in the end, it simply transforms text (a template) to text (a result JSON). With a decent unit test coverage, it's not magic anymore. JavaScript resolvers without testing utilities would be equally or more problematic.

If you have any questions, I am happy to try to answer them. This GH issue is not the right place for that, but feel free to ping me on twitter or via email.

P.S. A few more reviewers are always welcome for our book :)

PatrykMilewski commented 3 years ago

👍 for ULID support in utils

joekendal commented 3 years ago

feature request: Include SHA-1 in the $util package

cespin commented 3 years ago

Please Cloud Jesus, let this feature be delivered as soon as possible 👏 😆

Joking aside this would be amazing, the resistance towards VTL that many people have is a problem.

dpkmc1960 commented 2 years ago

When can we expect this functionality in preview?

joekendal commented 2 years ago

+1 KSUID

jamesonwilliams commented 2 years ago

Amazed this is still open after a year, and with hundreds of customer reactions.

The management of this service needs improvement.

The AppSync engineers are passionate about the product and very opinionated, but sometimes their management team fails to recognize when it’s the right time to deliver impactful features for customers. Sometimes when critical issues like this are pending, AppSync management will choose to prioritize lower value tasks, which results in conflicts with customers, who are left frustrated with this product.

kyptov commented 2 years ago

@jamesonwilliams I want this feature too, but I disagree that it is something with high priority.

You can always point your resolver(s) to lambda and use JS, TS and many others. Yes it will be cost you a little bit more, but it is possible to achieve.

We are using single lambda for all resolvers, so it stays hot most of the time.

ndejaco2 commented 2 years ago

👍 for ULID support in utils

We have just added a new VTL utility for generating a new ULID:

$util.autoUlid() : String Returns a 128-bit randomly generated ULID (Universally Unique Lexicographically Sortable Identifier). Doc: https://docs.aws.amazon.com/appsync/latest/devguide/resolver-util-reference.html

joekendal commented 2 years ago

@ndejaco2 thanks is KSUID an option in the future?

ndejaco2 commented 2 years ago

@ndejaco2 thanks is KSUID an option in the future?

Currently there is no KSUID utility but we will also put that in the backlog!

sam-goodwin commented 2 years ago

Hi all, I built a solution to this problem that allows you to build Appsync Resolver Pipelines with native TypeScript.

Take a look: https://github.com/sam-goodwin/functionless

ndejaco2 commented 2 years ago

Hi, we have launched the KSUID utility today: https://aws.amazon.com/about-aws/whats-new/2022/05/aws-appsync-simplifies-graphql-api-development-graphql-utility-helper-library/

vicary commented 2 years ago

@ndejaco2 Thanks the AppSync team for making this happen. Albeit you left us no choice but to climb the learning curve of Velocity, we still want this feature for the sake of simplicity in our tech stacks. This really aids recruitment and team management in the long run.

atomos commented 2 years ago

Even more excited about the new logging functionality - that solves a huge pain point in developing VTL templates. Thanks!

ebisbe commented 2 years ago

@atomos you can test with Jest ( and others I suppose ) with the amplify-appsync-simulator package.

A simple test looks like

const when = require('../steps/when')
const path = require('path')

const mappingTemplate = 'Guest.user'
const requestTemplatePath = path.resolve(__dirname, `../../mapping-templates/${mappingTemplate}.request.vtl`)

describe(`[RESOLVER] > ${mappingTemplate} template`, () => {
  it('[REQUEST]', () => {
    const context = {
      source: {
        userId: 'userId'
      }
    }
    const response = when.we_invoke_an_appsync_template(requestTemplatePath, context)

    expect(response.result).toEqual({
      version: '2018-05-29',
      operation: 'GetItem',
      key: {
        PK: { S: 'USER#userId' },
        SK: { S: 'USER#userId' }
      },
      consistentRead: true
    })
  })

  // [RESPONSE] => prevResult.response.vtl
})
RawadHaber commented 2 years ago

Very much looking forward to this! I (and I'm sure many others) find VTL lacking a bit in terms of developer experience esp when writing templates in VsCode.

volkanunsal commented 2 years ago

@RawadHaber

Check out: https://functionless.org/docs/concepts/appsync/usage

ndejaco2 commented 2 years ago

@ndejaco2 Thanks the AppSync team for making this happen. Albeit you left us no choice but to climb the learning curve of Velocity, we still want this feature for the sake of simplicity in our tech stacks. This really aids recruitment and team management in the long run.

Absolutely! We certainly understand the high learning curve of Velocity. While we continue to release utils like this to make it easier to use Velocity, we are simultaneously working on providing mechanisms to enable you to use JavaScript instead of Velocity for your resolvers.

FelixRelli commented 2 years ago

@ndejaco2 Do you have time estimate for this?

PatrykMilewski commented 2 years ago

Absolutely! We certainly understand the high learning curve of Velocity. While we continue to release utils like this to make it easier to use Velocity, we are simultaneously working on providing mechanisms to enable you to use JavaScript instead of Velocity for your resolvers.

@ndejaco2 Do you have time estimation for this?

Probably 1 month up to 5 years from now

Sunac commented 2 years ago

Absolutely! We certainly understand the high learning curve of Velocity. While we continue to release utils like this to make it easier to use Velocity, we are simultaneously working on providing mechanisms to enable you to use JavaScript instead of Velocity for your resolvers.

@ndejaco2 Do you have time estimation for this?

Probably 1 month up to 5 years from now

But look on a bright side. By then, you'll master VTL (a limited though) and maybe even make good money as an AppSync consultant :smile:

samuelcastro commented 2 years ago

@ahmadizm Hi, this is really great. Is there a timeline to releasing this feature?

carlos-vh commented 2 years ago

Alt Text

cespin commented 2 years ago

I just spent two days fixing a VTL template. It turns out that if you \" in a variable and you then try to use that variable the whole thing goes kaboom.

That little detail plus the fact that when invoking Step Functions using a HTTP datasource, the request mapping template needs the input parameter in one line made me lose a chunk of my life. I ended up having to handle all possible cases of my input in a massive if-else-if-else block. If you wanna try this out try invoking Step Functions with App Sync and send it a big input that deserves several lines of code. If you wanna spice things up try to make the contents of that input dynamic and you'll see what I mean 🤯

I understand that VTL is performant but the complications it brings don't make sense.

For those advocating for the Lambda integration: the goal is Lambdalessness for the sake of "many things" plus performance which is key for web application APIs (probably the main use case for AppSync).

A very thin Javascript environment to run resolvers would be perfect. Kind of what CloudFront did with CloudFront functions. That runtime doesn't have a lot available but is blazing fast.

SchollSimon commented 2 years ago

@cespin i also agree, a good sample of a thin js env is their cloudfront function ide. I found it much easier to apply a few limitations to a know coding env

onlybakam commented 2 years ago

Update

Based upon feedback to this RFC and prototypes built to evaluate different potential ways to provide the ability to use JavaScript instead of VTL to implement resolver business logic, we have refined our approach and are now evaluating supporting JavaScript and Typescript in the AppSync service. We aim to make coding resolvers less error-prone, and more accessible to all developers.

The feature will allow developers to define their AppSync Resolver and AppSync Functions using a request function, and a response function. Each function would take a context object as an argument, and return a JSON value. For the request, the function returns the JSON object that AppSync uses to construct a data source request. For the response, the function returns the JSON object that returns data for the resolved field. Developers will be able to save their JavaScript or Typescript code directly to AppSync. If saving the resolver is not successful and configuration fails, a detailed error message will be returned in real-time.

Consider this sample code for a DynamoDB resolver as an example:

// file:resolver.ts

import { Context, $util, DynamoDBQuery, DynamoDBQueryResult } from '@aws-appsync/types'

export function request(ctx: Context) {
  const { username: owner, claims } = ctx.identity
  const { username, blogId } = ctx.arguments
  console.log(`received request for ${owner}`)

  if (username !== owner) {
    console.error(`un-allowed operation with ${username}`)
    $util.unauthorized()
  }

  const query: DynamoDBQuery = {
    version: '2017-02-28',
    operation: 'Query',
    query: {
      expression: '#k = :k and begins_with(#s, :s)',
      expressionNames: { '#k': 'id', '#s': 'blogId' },
      expressionValues: $util.dynamodb.toMapValues({ ':k': owner, ':s': `${blogId}#` }),
    },
  }

  return query
}

export function response(ctx: Context) {
  const now = $util.time.nowISO8601()
  const result = ctx.result as DynamoDBQueryResult<Post>

  let items = result.items.filter((item) => item.comments > 50)
  console.log(`retrieved ${items.length} popular posts from ${ctx.arguments.blogId}`)

  items = items.map((item) => {
    item.fetchedOn = now
    return item
  })

  return {
    items,
    nextToken: result.nextToken,
  }
}

You would then be able to define your resolver with the AWS CLI as follows:

$ aws appsync create-resolver --api-id <id> \
  --type-name "Query" --field-name "listPosts" \
  --data-source-name "postTable"
  --code file://resolver.ts

You may want make use of utilities in your code. It will be possible to write and re-use functions, and import local code. From the example above, you can refactor the code to extract the ownership check to it’s own utility file called local.ts

// file: local.ts

import { $util } from '@aw-appsync/types'

export function checkOwner(owner: string, username: string) {
  if (!owner || username !== owner) {
    console.error(`un-allowed operation with ${username}`)
    $util.unauthorized()
  }
}

You could then import the file in your resolver.ts file:

// file:resolver.ts

import { Context, $util, DynamoDBQuery, DynamoDBQueryResult } from '@aws-appsync/types'
import { checkOwner } from './local.ts'
import { queryBuilder } from './another-library.ts' // some other library

export function request(ctx: Context) {
  const { username: owner, claims } = ctx.identity
  const { username, blogId } = ctx.arguments
  console.log(`received request for ${owner}`)

  checkOwner(owner, username)

  const query: DynamoDBQuery = queryBuilder({ owner, blogId})

  return query
}

After bundling the files with esbuild:

$ esbuild resolver.ts --bundle --outfile=out.ts --format=esm --external:'@aws-appsync*'

You can create or update your resolver in the same way

$ aws appsync update-resolver --api-id <id> \
  --type-name "Query" --field-name "listPosts" \
  --data-source-name "postTable"
  --code file://out.ts

When working with pipeline resolvers, you can write your AppSync function resolver code in the same way, and define your functions via the CLI or using your favorite IaC tool. For resolvers in pipeline configuration, the request function corresponds to the before mapping template and the response function corresponds to the after mapping template.

$aws appsync create-function --api-id <id> \
  --name "function1" --data-source-name "postTable"
  --function-version "2018-05-29"
  --code file://fn1.ts

$ aws appsync update-resolver --api-id <id> \
  --type-name "Query" --field-name "listPosts" \
  --pipeline-config functions=function1-id \
  --code file://out.ts

You would be able to write your resolvers in either VTL or Javascript/Typescript in your API. We are evaluating giving you the option to convert your JavaScript code back to the equivalent VTL code.

Supported functionality

All of AppSync’s existing functionality will be available in JavaScript. You will be able to leverage AppSync’s $util and $extension utilities. To support developers coding in IDEs such as VS Code, we are evaluating providing definition files for AppSync’s utilities (e.g: @aws-appsync/types in the example above) that will make it easier to use them. This will provide documentation and auto-completion features from within your code. The tooling will also catch use of any unsupported or or unknown AppSync utility or extension, thus helping you avoid issues when your code is evaluated.

hints in editor

We are evaluating support of a subset of ECMAScript 6, and the majority of methods on primitive types and built-in objects (e.g.: string, numbers, object, and arrays). For example, we are evaluating support of most control flow structures (for-loops, conditional, switch statements, etc...), but may not support constructs such as while-loops or promises (because async use cases are not considered), and nested functions. To make it easy to catch these unsupported features in your code we are evaluating providing an eslint plugin (e.g.: eslint-plugin-appsync) and a set of recommended rules that will warn you of unsupported functionality in your code and provide guidance on alternatives if they exist. These rules will be included into the service and local tooling rules.

linting in editor

Testing

We are evaluating providing a test API that you will be able to use to validate and test your JavaScript resolvers. This will allow you to test your code against an environment that replicates AppSync’s behavior and evaluation. The API could be built to work similarly to the EvaluateMappingTemplate API for AppSync (more info on it in this blog post).

Consider the following command:

$ aws appsync evaluate-mapping-template --code file//out.ts --context "{}" --function request

A valid execution with logs could look like this.

{
   "logs": [
     "javascript:3:13 - a log line",
     "javascript:6:18 - another log line"
   ],
   "evaluationResult": "{\n    \"version\": \"2017-02-28\" ..."
}

As highlighted in the blog post referenced above, you would then be able to incorporate this test API in your favorite testing framework like Jest or Mocha.

Syntax errors caused by language lines of code will be surfaced in logs, while contract errors during evaluation with AppSync's runtime interacting with a Data Source will have a custom format. This structure is still to be determined and will be presented at a later time.

Limitations

While the solution will support imports, this functionality may be limited to local imports to begin with. Note that some existing limitations may still apply (e.g.: quotas). For instance, limitations would still apply to certain JavaScript operations which cannot run unbounded. For instance array.forEach would be bounded.

Conclusion

Please comment on this thread if you have some thoughts or suggestions on this feature. Let us know if there is any functionality that you think is missing or if the approach is not addressing your development needs.

volkanunsal commented 2 years ago

@onlybakam When is the new API going to be available?

stojanovic commented 2 years ago

@onlybakam thanks for the update. I have a few questions:

  1. Will this work with CloudFormation? I understand that it's easier to show (and probably implement) this with the AWS CLI, but many people use IaaC tools such as CloudFormation for their applications. It's very hard to manage any system with more than five components with AWS CLI.

  2. What are the benefits of transpilation? I know that implementing a new templating language is hard, but adding another layer to an already complicated system will not make it easier. There are now multiple things that can go wrong, such as:

    • Error in your JavaScript/TypeScript code
    • Error in translating the JavaScript/TypeScript template to the VTL template
    • Error in your VTL template
    • Error in your business logic.

    While I definitely prefer writing JavaScript and TypeScript, I don't see this solution as "less error-prone, and more accessible to all developers." It is already hard to deal with VTL resolvers that are a bit more complex, I do not see that this will help in large production applications, but I might be wrong, of course.

  3. If this implementation is a transpiler, why would you hide it behind the AWS CLI? I think it would make much more sense to publish it as an open-source library. There should be no hidden business logic in the transpiler, and open sourcing it would make the development experience much faster and more pleasant.

    You mentioned that the error while saving the resolver would result in "a detailed error message (that) will be returned in real-time." However, I don't see how that will work in real-time with the CloudFormation deployment, especially if the AppSync is in a nested stack. By open sourcing this tool, we would at least be able to use it to transpile (and maybe test) our templates in the build phase before we start the deployment process.

cc @awsed

onlybakam commented 2 years ago

hey @stojanovic , for 1: for brevity i showed CLI examples but could have been cloudformation or CDK. the goal is for this to work with cloudformation (AppSync supports cloudformation for its resources like resolvers); which is really important for developers.

for 2, and 3: we are still investigating details, and we havent settled on a mechanism or approach. The error message we are returning has to do with configuration. if the function definition you provide is not "correct" (still to be defined), we would provide an error message explaining the reason. The behavior would be the same with the CLI or cloudformation.

thdxr commented 2 years ago

~At runtime is it running under a JS runtime? Or is it getting compiled directly into the underlying VTL? I assume the former as I imagine there are things in JS you cannot do in VTL. Unless it just errors in those cases~

Read closer and I see you have the eslint plugin to disallow things not supported so it seems like it's transpiling to VTL?

onlybakam commented 2 years ago

we're still evaluating this, but because we may not be able to support all javascript features, we think an eslint plugin will be a good way of providing guidance when coding. idea is to avoid using unsupported features. Along with the type definition and auto-completion, this could be a good way of doing this.

vicary commented 2 years ago

Thanks @onlybakam for being transparent about the thought process!

Developer tools such as @aws-appsync/types and the new eslint rules are absolutely needed, this really aids development and improves DX!

If a new JavaScript runtime is not happening within AppSync, then I am with the previous comments. An open-source transpiler would be a much better way for debugging.

This works great in tandem with testings.

Instead of a JavaScript/TypeScript validator, I would rather suggest a VTL renderer instead. It can either lives inside AppSync as a hosted API method, or an offline renderer. It should renders the template outputs from a user specified input event object.

We currently deployed large VTL templates to translate an array of GraphQL inputs into OpenSearch queries, if such a VTL renderer is available, it opens up the possibilities of unit testing, which is not possible at the moment until e2e phase.

Because of the delayed nature when updating AppSync VTL templates, debugging is quite a pain because we have to repeatedly query a dev endpoint until we see an updated response.

ScottAwesome commented 2 years ago

Any word on a 100% code first approach? VTL is an actual blocker for us to adopt AppSync - nobody uses it enough to be an expert and AppSync has massive limitations for us without a code first approach, especially with resolvers, but it would be great to get out of these configuration languages like YAML and VTL and be able to do everything via JavaScript.

Not to mention proper unit testing is nearly impossible.

aman-hatcroft commented 2 years ago

@onlybakam . Thanks for the update.

If I understood it correctly, we are just creating an another layer for resolvers in which we can write code in JS / TS instead of VTL , but it would still have the same limitations of VTL.

For example: When I thought of JS resolvers, I thought we would now be able to make multiple DB calls in the same request resolver ( because we would be using JS right ? ) , but to me it appears that it can't be done under JS too , unless you use the pipeline resolvers or Lambda resolvers.

I get a feeling that Appsync would just translate your JS code into VTL templates under the hood . As a developer, I feel that I would still be limited in fully utilising the language we use to code everyday. We would still be limited to understand what's allowed in JS resolvers and what's not.

The tiny JS rules & runtimes makes sense on the least used features such as in Cloudfront functions , but in case of API, which are being developed or refactored on daily basis, having a pre-defined limitations on how to write JS resolvers doesn't make sense. To me it still looks prone to errors.

I also thought that the new JS resolvers would allow us to migrate some lambda resolvers ( a simple one, but not complex ) into JS resolvers, but I don't think that would be achievable with the new flow you presented.

Any thoughts ? I may be wrong as well in my understanding. Please correct me , If I understood it incorrectly.

laverdet commented 2 years ago

Even under this feature's most ambitious proposal you were never going to be able to make multiple database calls in a resolver because it doesn't fit into AppSync's architectural model. You will still need to render a request descriptor which is then passed onto the opaque DynamoDB database layer or the next resolver in the pipeline.

No doubt there's going to be a lot of unhappy campers with this implementation. For our team we had this fantasy that we would be able to use our GraphQL service to pre-sign S3 PutObject requests in order to replace Amplify's, quite frankly insane and insecure, storage feature. But there's not a chance that even 1 in 100 npm packages will run under this translation layer, so that is out of the question.

Even a seemingly reasonable goal of replacing the default multi-kilobyte pagination tokens with, say, an item's timestamp attribute is also impossible under this model.

The transpilation approach is an impressive engineering feat and probably the best the team can do given the circumstances. I fear that AppSync is just fundamentally flawed and without a radical redesign customers are going to continue feeling constrained.

atomos commented 2 years ago

I for one am very excited about this, and transpilation sounds like a totally reasonable approach to me. It accomplishes the main problems I have with VTL:

(1) eslint compatibility! This is my main pain point with VTL. Errors flagged in a code editor or at upload time (especially "any unsupported or or unknown AppSync utility or extension") will save a lot of trial and error testing.

(2) Code reusability is music to my ears!

(3) Javascript is more readable and much more widely understood than VTL.

(4) A testing API is good, but a localized test environment would be better. I'd prefer not to have my automated test suite dependent on a remote AWS API.

100% JS compatibility and support for multiple DB calls are understandably outside the scope of this simple translation layer. For that, we have lambda. I do agree with @laverdet that S3 integration is a huge missing link - IMO even more important than javascript support - but that's a separate issue. There's a GitHub issue tracking that request here.

LeoLapworthKT commented 2 years ago

The transpilation approach is an impressive engineering feat and probably the best the team can do given the circumstances. I fear that AppSync is just fundamentally flawed and without a radical redesign customers are going to continue feeling constrained.

It sounds like you want to run any code at all, including modules etc... so itsn't that the same as using the current lambda resolver (I feel like I'm missing something) ?

My take on this is about the responsibility model - here AWS is saying they support VTL (all security / patching / performance improvments etc are their problem). If this got opened to run anything then us as the customers have to take on a lot of that responsibility and frankly that's something to be avoided IMHO.

The approch they seem to be taking to my mind is best of both, I can write in TypeSciprt but AWS takes the responsility of managing the compile (to VTL) and run time (AppSync).

I would love a TS->VTL open source utility so I could run this locally for testing and the like but beyond that I don't want to take on more responsibility if possible.

sam-goodwin commented 2 years ago

I'm surprised to hear AWS's plan is to transpile TS to VTL. We already implemented that in functionless https://github.com/functionless/functionless and would be more than happy to separate it out into a standalone library and collaborate with aws. The last thing we need is another purely server side solution that can't be improved by the community (looking at you CloudFormation and step functions).

A better solution would be network constrained NodeJS isolates. Has the aws team explored that option?

vicary commented 2 years ago

We already implemented that in functionless https://github.com/functionless/functionless and would be more than happy to separate it out into a standalone library and collaborate with aws.

@sam-goodwin I'll be there for https://github.com/functionless/functionless/issues/257

matthias-pichler commented 2 years ago

A better solution would be network constrained NodeJS isolates. Has the aws team explored that option?

I feel like this would indeed be better because then one isn't constraint by which JS syntax can or cannot be compiled to VTL. A runtime similar to the one for CloudFront Functions could open up a lot more possibilities.

onlybakam commented 2 years ago

@vicary can you provide a bit more information about the renderer idea?

onlybakam commented 2 years ago

Regarding Lambda, as we pointed out initially:

It’s not possible to import external Node.js modules in order to maintain a lean and optimized AppSync runtime layer specifically for GraphQL workloads. If developers want to import modules or do anything more complex, the recommended approach is to use Direct Lambda Resolvers.

While we aim to make writing resolvers easy with typescript/javascript support, for complex scenarios Direct Lambda Resolvers (using AWS Lambda functions) is still the recommended approach.

vicary commented 2 years ago

@vicary can you provide a bit more information about the renderer idea?

@onlybakam I was incorrect about it. The renderer idea is already available via AppSync#evaluateMappingTemplate.

The issue is that we specifically matches our aws-sdk version with the current Lambda runtime to reduce bundle size. It also prevents our team from using methods which is not yet available. The method evaluateMappingTemplate is such an example in the current Node.js runtime aws-sdk@2.1055.0.

It's a mild inconvenience, but I believe we can work with it. Thanks for the heads up!