In this article, we
will learn how to create an AppSync API with the Lambda function. I will show you how
a lambda function is written using the Dotnet core that performs business logic
based on invoking graphical field operation. AppSync enables us to use AWS
Lambda to resolve any graphing field.
Technology
Framework- Visual studio 2019
DotNet core and backend database
MongoDB
Prerequisites
You should have the
basic knowledge of .Net Core with C#, Visual Studio, and NoSQL database
understanding will be additional.
This article divided into three parts.
Part 1- Lambda Function development
using DotNet core.
Part 2- Deployment on AWS
Part 3 – AppSync API
Lambda Function development using DotNet core
STEP 1- First, you
install AWS Toolkit for Visual Studio. The AWS Toolkit is an extension for
Microsoft Visual Studio makes it more accessible for developers to develop, debug, and
deploy .NET applications using Amazon Web Services. It provides the templates
that able you to get started faster and be more productive when building AWS
applications. AWS Toolkit supports to VS 2013- VS 2015 and VS 2017 - VS 2019
(For both operating system windows and MAC). Follow the
Link(https://aws.amazon.com/visualstudio/) and download/install it According to
your convenience. If your MongoDB is installed and working, then good else you
can follow the link How to Install MongoDB on windows.
STEP 2- Open Visual
Studio and Click on the new project
Search with keyword
"AWS Lambda Function" select the project marks as below
Now, Configure you're a project such as AWSLambdaResolver, click on the create button it will create a project at your path location.
STEP 3- Add Startup class and
replace with the code given below.
Note: It may be some assembly error
arises in code, to remove this issue you have to add this DLL from NuGetPackge.
You may have to install the DLL
listed below
1- Microsoft.AspNetCore
2- Microsoft.AspNetCore.Mvc
3- Microsoft.AspNetCore.Identity --
if you want some security on your function
4- Microsoft.EntityFrameworkCore--
if you are using entity framework
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using
Microsoft.Extensions.Configuration;
using
Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using AWSLambdaResolver.Models;
namespace AWSLambdaResolver
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration
= configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to
add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<DatabaseSettings>(
Configuration.GetSection(nameof(DatabaseSettings)));
services.AddSingleton<IDatabaseSettings>(sp =>
sp.GetRequiredService<IOptions<DatabaseSettings>>().Value);
services.AddSingleton<BookService>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to
configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change
this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
}
|
Step 4- In this step, I will create the entity, first, create Models directory in the project root. Add the Book class
and copy-paste the following code.
You have to install the package of
MongoDB.Driver using NuGetPackges
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System;
using System.Collections.Generic;
using System.Text;
namespace AWSLambdaResolver.Models
{
public class Book
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string _id { get; set; }
//[BsonElement("Name")]
public string BookName { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
public string Author { get; set; }
public string appsyncResponse { set; get; }
}
}
|
And add another class MyRequest--
this object use in function which is will be mapped from AppSync
using System;
using System.Collections.Generic;
using System.Text;
namespace AWSLambdaResolver.Models
{
public class MyRequest
{
public string RequestType { set; get; }
public Book book { set; get; }
}
}
|
Step 5- In this step
we will add a configuration model, where we will provide the database
configuration(appsettings.json).
As we know the DotNet
Core follows the Dependency Injection architecture, we can push the database
credential from the Lambda Function or also can configure the database in the startup.cs class.
{
"DatabaseSettings":
{
"BooksCollectionName": "Books",
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName":
"BookstoreDb"
},
"Logging": {
"IncludeScopes":
false,
"Debug": {
"LogLevel": {
"Default":
"Warning"
}
},
"Console": {
"LogLevel": {
"Default":
"Warning"
}
}
}
}
|
Step 6 - In this step, we will add a DatabaseSettings.cs file to the Models directory with the
following code. The preceding DatabaseSettings class is used to store the
appsettings.json file's DatabaseSettings property values. The JSON and C#
property names are named identically to ease the mapping process.
using System;
using System.Collections.Generic;
using System.Text;
namespace AWSLambdaResolver.Models
{
public class DatabaseSettings :
IDatabaseSettings
{
public string BooksCollectionName { get; set; }
public string ConnectionString { get; set; }
public string DatabaseName { get; set; }
}
public interface IDatabaseSettings
{
string BooksCollectionName { get; set; }
string ConnectionString { get; set; }
string DatabaseName { get; set; }
}
}
|
Step 7- In this step, we will create a CRUD operation service in the Models folder to add the Services.cs
class and copy-paste the following code. In the preceding code, an
IDatabaseSettings instance is retrieved from Dependency Injection via
constructor injection.
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Text;
namespace AWSLambdaResolver.Models
{
public class BookService
{
private readonly IMongoCollection<Book> _books;
public BookService(IDatabaseSettings settings)
{
var client = new
MongoClient(settings.ConnectionString);
var database =
client.GetDatabase(settings.DatabaseName);
_books =
database.GetCollection<Book>(settings.BooksCollectionName);
}
public List<Book> Get() =>
_books.Find(x
=> true).ToList();
public Book Get(string id) =>
_books.Find<Book>(x => x._id == id).FirstOrDefault();
public Book Create(Book x)
{
_books.InsertOne(x);
return x;
}
public void Update(string id, Book bk) =>
_books.ReplaceOne(x
=> x._id == id, bk);
public void Remove(Book bk) =>
_books.DeleteOne(x => x._id == bk._id);
public void Remove(string id) =>
_books.DeleteOne(x => x._id == id);
}
}
|
Step 8- now we will
understand the Startup.ConfigureServices, first, we will talk about the
following code section.
services.Configure<DatabaseSettings>(
Configuration.GetSection(nameof(DatabaseSettings)));
services.AddSingleton<IDatabaseSettings>(sp =>
sp.GetRequiredService<IOptions<DatabaseSettings>>().Value);
|
The configuration instance to which
the appsettings.json file's DatabaseSettings section binds is registered in the
Dependency Injection container. For example, a DatabaseSettings object's
ConnectionString property is populated with the DatabaseSettings:ConnectionString
property in appsettings.json.
The IDatabaseSettings interface is
registered in Dependency Injection with a singleton service lifetime. When
injected, the interface instance resolves to a DatabaseSettings object.
services.AddSingleton<BookService>();
|
In the above line of code the
BookService class is registered with Dependency Injection to support
constructor injection in consuming classes
Step 9- Create an object class to
consume the CRUD service operation (Business Layer coding or if you are using
the API controller direct you can code on in the controller)
using System;
using System.Collections.Generic;
using System.Text;
namespace AWSLambdaResolver.Models
{
public class CrudOperation
{
private readonly BookService _bookService;
public CrudOperation(BookService bookService)
{
_bookService =
bookService;
}
public List<Book> Get() =>
_bookService.Get();
public Book Get(string id)
{
var x = _bookService.Get(id);
if (x == null)
{
return null;
}
return x;
}
public Book Create(Book x)
{
_bookService.Create(x);
return new Book() { _id = x._id.ToString() };
}
public string Update(string id, Book bk)
{
var x = _bookService.Get(id);
if (x == null)
{
return "no
record found to update";
}
_bookService.Update(id, bk);
return "Updated";
}
public string Delete(string id)
{
var x = _bookService.Get(id);
if (x == null)
{
return "No
records found to delete";
}
_bookService.Remove(x._id);
return "Records
Deleted";
}
}
}
|
Step 10- In this step
we will work on Lambda Function method where we will implement the CRUD
operation that will call the different service class methods on a request basis.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Amazon.Lambda.Core;
using AWSLambdaResolver.Models;
// Assembly attribute to enable the Lambda function's JSON input
to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
namespace AWSLambdaResolver
{
public class Function
{
/// <summary>
/// A simple function that takes a
string and does a ToUpper
/// </summary>
/// <param
name="input"></param>
/// <param
name="context"></param>
/// <returns></returns>
public object FunctionHandler(MyRequest input, ILambdaContext context)
{
IDatabaseSettings bs = new DatabaseSettings();
bs.BooksCollectionName = "Books";
bs.ConnectionString = "your db connection string";
bs.DatabaseName = "your database name";
BookService
mybookservice = new
BookService(bs);
CrudOperation
crd = new CrudOperation(mybookservice);
switch (input.RequestType)
{
case "AddBook":
{
input.book.appsyncResponse = "Book
Added";
return crd.Create(input.book);
}
case "Update":
{
input.book.appsyncResponse = "Records
Updated";
input.book._id = crd.Update(input.book._id, input.book);
return input.book;
}
case "Delete":
{
input.book.appsyncResponse = "Record
Deleted";
input.book._id = crd.Delete(input.book._id);
return input.book;
}
case "ReadById":
{
return crd.Get(input.book._id);
}
case "ReadAll":
{
return crd.Get();
}
default:
{
input.book.appsyncResponse = "No
Action";
return input.book;
}
}
}
}
}
|
Step 11-
Build your code now, if build successfully then it is ready to deploy on the
AWS Lambda Function. To deploy on the AWS Lambda function please follow the
following steps.
AWS Lambda Function deployment
Step 1- Create AWS account using the
link console.aws.amazon.com, it is free for one year (but have to use carefully
some services are chargeable)
Step 2- In this step, we will find
out needed stuff for deployment
Right-Click on ”AWSLambdaResolver” Ã Publish to AWS Lambda
Click on “Publish to AWS Lambda”,
the following window will open as below
Account
Profile to use:
Here you will set up the account, once you click on the icon the following the window will open.
The following option will be shown
on window
1- Profile
Name: This is the required field which will be set up in your visual studio that will be used for
further. This is a profile name of “default” allows the SDK to find credentials
when no explicit profile name is specified in your code or application
configuration setting.
2- Storage
Location: Using the
user credential file, the profile AWS credential will be stored in the <home directory> \.aws\credential file. The profile
accessible to all AWS SDKs and tools.
3- Access
Key ID: Access Key
ID provided by AWS
4- Secret
Access Key:
Provided By AWS
5- Account
Number: Provided by
AWS
6- Account
Type: Choose as
default
You
can follow the link, how to get the credential of the AWS: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html?icmpid=docs_iam_console
Region: Select the region where you want to
deploy the Lambda Function, if you are working on the business purpose then you
should choose the option as per your pricing ability.
Function Details:
Function
Name: Set as the
default
Configuration:
Configuration of
have two option,
·
Release
·
Debug
Choose as per your requirement.
Assembly
Name: Set as
default
Account
Number: You can get an account number from your AWS account
Account
Type Name: Set as
default
Method
Name: Set as
default
If all the above options filled/chosen
properly then, click on the Next button. Action on the next window is given below.
Permission
Role
Name: Select an IAM
Role to provide AWS credentials to our lambda function allowing access to AWS
Service like S3.
Execution
Memory:
By default its 256,
put the memory size as per your requirement.
Timeout:
Set time out in
seconds.
VPC
(Virtual Private Cloud)
VPC
Subnet: Set the PVC
to run your function on the private network
Security
Groups: You should
always aim to restrict access with your security group to help maintain
restriction of access at the protocol and port level.
For
the development you can leave this option.
Debugging
and Error Handling
This option is optional where you
can set up the error handling for the Lambda Function.
Environment
Choose as default
Now, finally, click on the Upload button, and wait for a while and
a new window will open in visual studio for the testing the function.
You can check your account where you
can see the Lambda Function has created. Form AWS Lambda Function you can test,
it is working or not.
If our Lambda Function is working
fine, it means we are ready to work for the AppSync Resolver.
AppSync API
To create
AppSync please follow the below steps
Step 1- Click, Servicesà in search type AppSync à AWS AppSync
The following window will open, click on the Create
API
Choose the
option Build From Scratch
Step 2- Create Resource: Configure API Name
and click to create a button.
Step 3- Now, your API is created.
Step 4- In this step, we will configure Data Source.
Click on
the create button and configure the Data
Source from Lambda Function. Fill
the required input as below image and click on the Create Button
Your Data
Source Created
Step 5- Now, we will configure the schema.
Copy-paste
the following code in the schema section and click on the Save Schema button
type Mutation {
AddBook(
_id:
ID!,
BookName:
String!,
Price:
Int,
Category:
String,
Author:
String
): Post!
}
type Post {
_id: ID!
BookName:
String!
Price: Int
Category: String
Author: String
appsyncResponse:
String
}
type Query {
UpdateBook(
_id:
ID!,
BookName:
String!,
Price:
Int,
Category:
String,
Author:
String
): Post
DeleteBook(_id:
ID!): Post
ReadByID(_id:
ID!): Post
ReadAll: [Post]
}
schema {
query: Query
mutation:
Mutation
}
|
Step 6- In this step, we configure Resolver one by for the AddBook,
UpdateBook, ReadByID, and ReadAll.
Resolver
means, we are mapping our GraphQL schema to Lambda Function input and output
results.
Configure Resolver for AddBook
method
Click on
Attached as marked in the image.
After Click
you will find the following window.
Data Source Name: Choose the Data Source Name you
have created in Step 4.
Configure the request mapping
template: Copy past
the following code.
{
"version":
"2017-02-28",
"operation":
"Invoke",
"payload": {
"RequestType": "AddBook",
"book": $utils.toJson($context.arguments)
}
}
|
Here, the payload is the body where you will pass
the same structure as you passed in the testing of lambda. $context.arguments will
contains the mutation schema of GraphQL
Post {
_id: ID!
BookName: String!
Price: Int
Category: String
Author: String
appsyncResponse:
String
}
$utils.tojson()
function convert the GrphQL input into JSON.
You can see
it is the same structure of request we earlier test in Lambda Function.
Same as
AddBook Resolver, we will configure other resolvers also.
Configure Resolver for UpdateBook
Copy and
paste the following code
{
"version":
"2017-02-28",
"operation":
"Invoke",
"payload": {
"RequestType": "Update",
"book": $utils.toJson($context.arguments)
}
}
|
Configure Resolver for DeleteBook
Copy and
paste the following code
{
"version":
"2017-02-28",
"operation":
"Invoke",
"payload": {
"RequestType": "Delete",
"book": $utils.toJson($context.arguments)
}
}
|
Configure Resolver for ReadByID
Copy-paste
the following code
{
"version":
"2017-02-28",
"operation":
"Invoke",
"payload": {
"RequestType": "ReadById",
"book": $utils.toJson($context.arguments)
}
}
|
Configure Resolver for ReadAll
Copy-paste the following code
{
"version":
"2017-02-28",
"operation":
"Invoke",
"payload": {
"RequestType": "ReadAll"
}
}
|
You’re all
setup done. Now we will learn GraphQL query
GraprhQL Query
Add Query
Add Book using GraphQL
query. I will use the AWS console to run the GraphQL query.
mutation addPost {
AddBook(
_id: "58f5e3ea03102d23ec654389"
BookName: "My test
Book"
Price: 120
Category: "Book"
Author: "Dilip
singh"
) {
_id
}
}
|
Above Query will return the _id of creating a record.
Result:
{
"data": {
"AddBook": {
"_id":
"58f5e3ea03102d23ec654389"
}
}
}
|
Update Query
I am updating the
above record.
query UpdateBook{
UpdateBook(
_id :"58f5e3ea03102d23ec654389"
BookName:"My update"
Price:12
Category:"Book1"
Author:"Dilip Singh"
){
_id
}
}
|
Result:
{
"data": {
"UpdateBook": {
"_id":
"Updated"
}
}
}
|
Read Query
Ready By ID: The fetch query I written below
query ReadById
{
ReadByID(_id:"58f5e3ea03102d23ec744389")
{
_id
BookName
Price
Category
Author
}
}
|
Result:
{
"data": {
"ReadByID": {
"_id":
"58f5e3ea03102d23ec744389",
"BookName":
"My Book 2",
"Price": 120,
"Category":
"Test 2",
"Author":
"Dilip singh"
}
}
}
|
Read All Query
Run the
following GraphQL Query
query ReadAll
{
ReadAll {
_id
BookName
Price
Category
Author
}
}
|
Result:
{
"data": {
"ReadAll": [
{
"_id":
"58f5e3ea03102d23ec744380",
"BookName":
"My Book",
"Price": 12,
"Category":
"Test",
"Author":
"Dilip"
},
{
"_id":
"58f5e3ea03102d23ec744355",
"BookName":
"My Book 2",
"Price": 120,
"Category":
"Test 2",
"Author":
"Dilip singh"
}
]
}
}
|
more accessibleHope this article helped you achieve your goals and a good education. Please subscribe to my
site for new updates and keep in touch through mail and comments.
No comments:
Post a Comment
Please do not enter any spam link in the comment box.