If reading that header fills you with joy and anticipation, you can probably skip down to You Will Need: and get building. If your reaction is closer to
What's a Datomic? Why would you ionize a cloud? Who is AppSync?
Fear not! Keep reading right here, and by the time we get to the You Will Need: part, you should understand why you might want to build one of what we have on offer in this repository.
After almost a decade of eating the world, REST has recently begun to see competiton in the form of GraphQL. Developed at Facebook, GraphQL is a model for querying and mutating data from an API endpoint that is very different from REST in several ways that I won't go into great detail about in this README. I will simply say that many people and organizations have found it to be a good fit for their problem space. Standing up a GraphQL server in AWS has historically been the province of running servers, either Docker containers in Elastic Container Service or plain EC2 machines, which are then hooked up to load balancers, monitored, patched, and so forth.
That changed last April 13 at React Amsterdam, when AWS announced the GA of AWS AppSync, which is a fully managed service for GraphQL APIs in much the same way that AWS API Gateway is a fully-managed service for REST. Since the announcement, interest in and attention to AppSync in the AWS and especially the #Serverless community has been, well... 
I could write a whole page just about this. Please start with a few resources: Datomic blog post announcing Ions ion-starter repository
Take two amazing tools, Datomic Cloud Ions and AWS Appsync, and smash them together into one really really great tool!
`
- A local Clojure dev environment that can run Clojure 1.9 and the new command-line tools/
tools.depsDocumentation for install and setup of Clojure is available here - An AWS account with a running Datomic Cloud cluster (free tier account Solo topology Datomic Cloud is 100% fine for this) Get up and running with Datomic Cloud using the instructions here
- That cluster's compute stack name

- To run the terminal commands for interacting with AWS CloudFormation in this
README, the AWS CLI It is also possible to use the AWS Web console to stand up CloudFormation resources if you prefer. - A checkout or fork of the Datomic
ion-starterexample that has been:pushed and:deployed to your Cloud instance as outlined in that repo'sREADME.md - A checkout or fork of this repository
- (optional) An install of GraphiQL or another GraphQL client
This repository contains AWS CloudFormation templates and Clojure source code to enrich a Datomic Cloud environment which already has loaded in the ion-starter ion fns with:
- an AWS AppSync GraphQL service with a schema that models the
ion-starterdataset - AppSync Data sources that expose the
datomic.ion.starter/items-by-typeanddatomic.ion.starter/add-itemlambdas to AppSync - AppSync Resolvers that let us query
items-by-typeand mutateadd-item - An AppSync Subscription that lets us track
items-by-typeover time - (optionally) an AWS Cognito UserPool to authenticate access
- (optionally) an AWS Cognito application
- (optionallly) a simple CLJS web client for the GraphQL API
- Start by spinning up a Datomic Cloud cluster and noting the name of the compute stack - this value will be a parameter to one of our CloudFormation templates.
- Deploy the
ion-starterschema, dataset, and ions, following the instructions in theion-starterrepo
In order for the AppSync stack deploy to succeed, the AWS Lambda functions for the items-by-type-json and items-by-type-gql ions need to exist in your AWS account. Fortunately, that's as simple as running a standard Ions deploy phase from this repo:
clj -A:dev -m datomic.ion.dev '{:op :push}' # note 'rev' value from return statement
clj -A:dev -m datomic.ion.dev '{:op :deploy :rev "<rev value from push>" :group "<your compute stack name>"}'Once this deployment is successful, it's time to stand up the AWS AppSync pieces!
This is the simplest way to stand up the AppSync infrastructure with a simple API-key based auth setup. If you don't have an existing method for authorizing users to hit an API you'd rather use, or aren't interested in combining AppSync with a more-robust user authentication scheme, use this method. Note that this will not work with the SPA or (planned future) React Native apps in this repository, it is only appropriate for "taking the API for a spin" with GraphiQL or another GraphQL client. For an example of a fully-functional full-stack Ion-backed app, see Option 2 below.
- Run
scripts/appsync-simple.sh, providing the desired cloudformation stack name, an api name, the region and the name of the compute stack as parameters:
./scripts/appsync-simple.sh <this stack name> <api name> <region> <compute stack name>This method is partially finished and partially documented. Watch this space. This method creates a real, production-ready application with authentication and user management provided by AWS Cognito. It is included in this repository because You can provide your own AWS Cognito User Pool and client to the AppSync API using this method.
- (optional) Launch a CloudFormation stack from the
templates/cognito.ymltemplate file to create the AWS Cognito infrastructure to control user authentication to the AppSync API:
aws cloudformation create-stack ...- Launch a second CloudFormation stack from the
templates/appsync.ymltemplate file to create the AWS AppSync resources:
aws cloudformation create-stack ...In order to access your new API with GraphiQL or other tools outside the AWS console, you will first need to create an API key (assuming you used Option 1). In the AWS Console, your AppSync APIs page will look like this:
Click on your API to navigate to its details page, where you will be told you don't have any API keys:
Simply click on the "settings page" link to go here:
Click the 'New' button and you have an API key:

Now, by providing that key as the value for an x-api-key header in curl, GraphiQL, or a client, you can access your AppSync API. The AWS AppSync console also provides a bundled GraphiQL window where you can try out queries:

Note that datomic.ion.starter/items-by-type* on its own doesn't return JSON but edn; in order to use the underlying items-by-type* fn in the larger AWS ecosystem, we are going to need a version that emits JSON. You can find that added to core.clj and ion-config.edn as items-by-type-json:
(defn items-by-type-json
"items-by-type starter ion modified to emit JSON for consumption by the AWS service ecosystem"
[{:keys [input]}]
(let [type (keyword (get (json/read-str input) "type"))
conn (d/connect (get-client) {:db-name "datomic-docs-tutorial"})]
;; NOTE that conn can - and should be - parameterized in production builds.
;; See the ion-starter repo and get-connection for a more production-ready approach with Datomic schema validation, etc.
(->> (items-by-type* (d/db conn) type)
json/write-str)))Note that the method for creating a connection in this and items-by-type-gql below is not an optimal approach for a production setting. Please review starter.clj in the ion-starter repo for a more "production-y" method, which is not duplicated here in order to avoid pulling in all the machinery for transacting the sample dataset and schema.
Now, if you execute this new function you will indeed get back JSON, but the JSON doesn't match the shape of the GraphQL schema - it is an array of arrays of values, but the schema wants an array of objects holding key/value pairs. There are two ways to handle this issue when it arises: change the shape of the received data in the Appsync resolver declaration itself, using the Apache Velocity templating language, or change the shape of the data your Ion sends to AppSync using Clojure. I present an example of each for items-by-type and leave you, dear reader, to reach your own conclusions about which one is better. To correctly present the shape of data we receive from items-by-type-json to GraphQL clients, we have to do this:
RequestMappingTemplate: |
{
"version": "2017-02-28",
"operation": "Invoke",
"payload": $utils.toJson($context.arguments.type)
}
ResponseMappingTemplate: |
\#set ( $itemsArray = [] )
\#foreach ( $item in $context.result )
\#set ( $itemMap = {} )
\#foreach ( $value in $item )
\#if ( $foreach.count == 1 )
$util.qr($itemMap.put("sku", $value))
\#{elseif ( $foreach.count == 2) }
$util.qr($itemMap.put("size", $value))
\#{elseif ( $foreach.count == 3) }
$util.qr($itemMap.put("color", $value))
\#{elseif ( $foreach.count == 4) }
$util.qr($itemMap.put("featured", $value))
\#end
\#end
$util.qr( $itemsArray.push( $itemMap ))
\#end
$utils.toJson($itemsArray)However, in order to make the shape of data correct in our ion itself, we need merely write this fn:
(defn items-by-type-gql
"GraphQL Datasource data-shape massager for items-by-type ion"
[{:keys [input]}]
(let [type (keyword (get (json/read-str input) "type"))
conn (d/connect (get-client) {:db-name "datomic-cloud-appsync"})]
;; NOTE that conn can - and should be - parameterized in production builds.
;; See the ion-starter repo and get-connection for a more production-ready approach with Datomic schema validation, etc.
(try
(->> (ion/items-by-type* (d/db conn) type)
(map #(zipmap [:sku :size :color :featured] %))
json/write-str)
(catch Exception e (str "Exception: |"
(.getMessage e)
"|, for input: |"
(str input)
"|, resolved type data: |"
type
"|")))))and then our response template looks like this:
RequestMappingTemplate: |
{
"version": "2017-02-28",
"operation": "Invoke",
"payload": $utils.toJson($context.arguments.type)
}
ResponseMappingTemplate: |
$utils.toJson($context.result)
- Further improve documentation
- Wire up the provided SPA with the Amplify library
- Build a React Native demo