GraphQL API using serverless container architecture

Software container is a lightweight executable that runs consistently on any infrastructure. It is an instance of software bundle called an image. The image is simply a bundle of operating system (OS) libraries and dependencies to run application code. Using OS virtualization, a platform provides the ability to instantiate container from the image. Docker is the most widely used platform to build images as well as run containers. In continuation to my previous posts on GraphQL, this post describes an approach to implement GraphQL API using serverless container architecture on AWS cloud. I recommend you to go through serverless GraphQL API post before reading further.

Prerequisites

  • Understanding of web APIs and its role in a web architecture
  • Understanding of GraphQL fundamentals
  • Experience with Python programming language (Python3)
  • Understanding of containerized application architecture
  • Experience with AWS and AWS container services such as ECR, ECS and Fargate
  • Understanding of infrastructure as code (IaC) and AWS CDK to achieve that on AWS cloud

Why build the API as a container?

As I mentioned in my previous post, AWS provides AppSync as a fully managed GraphQL API. Then the question is, why build it as a container? AppSync is well integrated and has many advantages but it lacks few basic features making it very difficult to implement and support.

1. Lack of multi-language support

AppSync only supports Apache Velocity Template Language (VTL) to write resolvers so it results into steep learning curve for a new team member. AWS has introduced few features to overcome this, such as direct lambda resolvers. However, lambda incurs additional cost and introduces one more integration layer.

2. Lack of Test-driven Development (TDD) support

Test-driven development is the most widely used approach these days but currently it is not possible to utilize TDD approach for AppSync implementation

3. Lack of debugging tools

Currently, it is not possible to debug AppSync resolvers line by line. It is possible to write log messages or investigate AWS logs but it takes longer to debug any logic implemented in AppSync using VTL

We can overcome these limitations and also leverage scalable architecture by implementing GraphQL API using serverless container architecture. This post provides the details to build the API in Python using AWS ECS and Fargate

The API implementation

Create the project directory and go to that directory

mkdir GraphQLServer-Container-AWS
cd GraphQLServer-Container-AWS

Initialize AWS infrastructure code using AWS CDK

cdk init app --language python

Activate Python virtual environment so you can install required Python packages

source .venv/bin/activate

Install Python packages required for CDK

pip install -r requirements.txtCode language: CSS (css)

Following files and folder will be created for the infrastructure

Files and folder for GraphQL API infrastructure as code in AWS CDK

Now, create GraphQL API project folder inside GraphQLServer-Container-AWS

mkdir GraphQLAPI
cd GraphQLAPI

Follow the steps from my previous post to create schema and resolvers. You can select schema-first or code-first approach. Rename __init__.py to main.py and replace existing code with the following

from ariadne import make_executable_schema, load_schema_from_path
from ariadne.asgi import GraphQL
from resolvers import resolvers

schema = make_executable_schema(
    load_schema_from_path('./Schemas/'), 
    resolvers)
    
app = GraphQL(schema, debug=True)Code language: JavaScript (javascript)

Install Uvicorn server

pip install uvicorn

Launch GraphQL API

uvicorn main:appCode language: CSS (css)

Now that the API is working locally, create the Dockerfile


FROM python:3.8-alpine

# Keeps Python from generating .pyc files in the container
ENV PYTHONDONTWRITEBYTECODE=1

# Turns off buffering for easier container logging
ENV PYTHONUNBUFFERED=1

# Install pip requirements
COPY requirements.txt .
RUN python -m pip install -r requirements.txt

WORKDIR /app
COPY . /app

CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
Code language: PHP (php)

Ensure that GraphQLAPI folder has its own requirements.txt

ariadne==0.14.1
uvicorn==0.17.5

Build the docker image locally, instantiate the container locally and validate

docker build --tag graphqlsrv .
docker run --publish 80:8000 graphqlsrv
Code language: CSS (css)

After testing the image locally, go to CDK stack and write the code to create AWS resources

from aws_cdk import (
    Stack,
    aws_autoscaling as autoscaling,
    aws_ec2 as ec2,
    aws_ecs as ecs,
    aws_ecs_patterns as ecspatterns,
    CfnOutput
)
from constructs import Construct

class GraphQlServerContainerAwsStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        #prefix for name of all resources
        nameprefix = "GraphQLContainer"
        
        #create VPC
        vpc = ec2.Vpc(
                    self, 
                    nameprefix + "VPC",
                    max_azs=2)
        
        #create cluster
        cluster = ecs.Cluster(
                    self,
                    nameprefix + "Cluster",
                    vpc=vpc)

        #create autoscaling group
        asg = autoscaling.AutoScalingGroup(
                    self,
                    nameprefix + "ASG",
                    instance_type=ec2.InstanceType("t2.micro"),
                    machine_image=ecs.EcsOptimizedImage.amazon_linux2(),
                    vpc=vpc)
        
        capacityprovider = ecs.AsgCapacityProvider(
                    self,
                    nameprefix + "ASGCP",
                    auto_scaling_group=asg)

        cluster.add_asg_capacity_provider(capacityprovider)

        #create ecs fargate from local image
        ecsfargateservice = ecspatterns.NetworkLoadBalancedFargateService(
                    self,
                    nameprefix + "FargateService",
                    cluster=cluster,
                    task_image_options={
                        'image':ecs.ContainerImage.from_asset('./GraphQLAPI')
                    })
        
        ecsfargateservice.service.connections.security_groups[0].add_ingress_rule(
                    peer=ec2.Peer.ipv4(vpc.vpc_cidr_block),
                    connection=ec2.Port.tcp(80),
                    description="Allow HTTP inbound from VPC"
        )

        #output load balancer DNS
        CfnOutput(self, nameprefix + "LoadBalancerDNS", value=ecsfargateservice.load_balancer.load_balancer_dns_name)

The infrastructure code here creates load balanced ECS cluster with Fargate. This helps to achieve fully managed serverless architecture for the container application. You can change the capacity and other properties to match your workload

Synthesize the infrastructure code

cdk synth

If you have not deployed with AWS CDK in your AWS account before, you will need to bootstrap first before the deployment

cdk bootstrap
cdk deploy

After successful deployment, copy the LoadBalancerDNS and launch it in a browser to validate API in the GraphQL playground

Complete source code with unit test cases for both API and infrastructure is located at my Github repo

https://github.com/vizeit/GraphQLServer-Container-AWS.gitCode language: JavaScript (javascript)