# AWS Cdk kullanarak Serverless uygulamamızı nasıl oluştururuz?

**AWS CDK (AWS Cloud Development Kit)**, uygulamalarımızı modellemek için ortak programlama dillerini (typescript, javascript, python, java, go, c#) kullanarak bulut ortamını geliştirme sürecini hızlandırır. AWS CDK ile aws paneli üzerinden yaptğımız her süreci kodlayarak yapabiliriz. EC2 oluşturmak, RDS oluşturmak, VPC oluşturmak, S3 Bucket oluşturmak, Certificate Manager ile bir SSL oluşturmak, Route53 üzerinde bir subdomain eklemek ve tüm aws kaynaklarını birbirleri ile ilişkilendirmek gibi bir çok süreci kodlayarak yapabiliriz.

Bugünkü yazımızda **AWS CDK** ile serverless uygulama geliştireceğiz. **Serverless compute**, bir sunucunun sorumluluğunu almadan uygulamalarımızı ya da servislerimizi derlemeyi ve çalıştırmayı sağlayan sistemlerdir. **Serverless** ile **uygun maliyetli**, **esnek, ölçeklenebilir**, **her zaman kullanılabilir** sistemler tasarlayabiliriz. Tüm bunları yaparken sunucu yönetimi (kurulum, bakım, güvenlik) gibi önemli bir sorumluluğuda AWS bırakırız.

Bu örnek projemizde bir **apigateaway** servisi oluşturacağız ve bu oluşturduğumuz **apigateaway**, kullanıcıların isteklerini karşılayacak. Gelen istekleri **lambda** servislerine yönlendirecek. Lambda servisleri **dynamodb** üzerinde okuma ya da yazma işlemlerini yapacak ve cevabı geri döndürecek. Tüm bu süreçleri, typescript dili ve aws cdk kütüphanesi ile kodlayarak oluşturacağız. Modellediğimiz bulut altyapısınıda **cloudformation** ile deploy edeceğiz.

AWS CDK, Serverless ve yapacağımız uygulama hakkında kısaca bahsettik. Şimdi yapacağımız uygulamaya geçelim. Bir AWS Cdk uygulaması oluşturabilmemiz için bilgisayarımızda **aws-cdk** npm paketi global olarak yüklü olması gerekiyor.

npm install -g aws-cdk

Yukarıdaki kod ile **aws-cdk** paketini bilgisayarımıza indiriyoruz. Daha sonra **mkdir** ile yeni bir klasör oluşturuyoruz ve **cd** ile o klasörün içerisine geçiyoruz. **cdk init** ile bir aws cdk uygulaması oluşturuyoruz ve projenin dilinin **typescript** olacağını belirtiyoruz.

mkdir ProductServerlessApplication  
cd ProductServerlessApplication   
cdk init --language typescript

Bu işlem sonunda cdk uygulamamız oluşmuş oluyor. Uygulamayı vscode ile açtığımızda uygulamanın yapısı aşağıda görebilirsiniz.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966630147/29706170-1a9d-414a-a2a6-f76276efa8ed.png)

cdk application structure

**bin** klasörü genişlettiğimizde **product\_serverless\_application.ts** dosyasını görüyoruz. Bu dosyayı açtığımızda bir **cdk.App** nesnesi ve bir alt satırda ise **ProductServerlessApplicationStack** nesnesinin oluşturulduğunu görüyoruz.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966631973/38fdb442-f273-4bc7-888e-8511d664126d.png)

**lib** klasörünü genişleştiğimizde **product\_serverless\_application-stack.ts** adında bir dosya bulacağız. Bu dosyayı açtığımızda **cdk.Stack** miras alan bir **ProductServerlessApplicationStack** sınıfı görüyoruz.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966633953/2ec30457-8c49-40f5-81ec-045c8bbb15cd.png)

Bir cdk uygulamasının yapısı aşağıdaki görseldeki gibidir. Her **AWS CDK** uygulamasında bir **App** ve bir ya da birden fazla **Stack** olabilir. **cdk.Stack** AWS **CloudFormationdaki** Stacklere karşılık gelir. Her bir **stack** deployment birimidir. **Stackleri** birbirinden bağımsız bir şekilde deploy edebiliriz ancak bir stack kapsamında tanımlanan tüm AWS kaynakların tümü birlikte deploy edilecektir. **Construct,** aws cdk uygulamasındaki temel yapı taşıdır. Her bir construct, aws’deki bulut bileşinini temsil eder. Bir **construct** içerisinde bir ya da birden fazla aws kaynakları bulunabilir. Bir **stack** içerisinde bir ya da birden fazla **Construct** olabilir.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966636139/0ceafd9d-c9a4-49cf-9bed-c219ee6d2993.png)

AWS CDK uygulamasındaki temel konseptlerden kısaca bahsettikten sonra örnek uygulamamızda bir app, bir stack ve bir adet construct bulunacak.

**lib** klasörü içerisine **modules** adında bir klasör oluşturdum. **modules** içerisine **products** adında başka bir klasör oluşturdum. Ayrıca hataları yönetebilmek için **modules** içerisine **api-error.ts** dosyasını oluşturdum.

export class ApiError extends Error {  
  statusCode: number = 0;  
  message: string = "";  
  
  constructor(statusCode: number, message: string) {  
    super();  
    this.statusCode = statusCode;  
    this.message = message;  
  }  
}

**products** klasörü altına **services** adında bir klasör oluşturdum. services klasörüne **lambda servislerimizi** koyacağız. **products** klasörü içerisine ayrıca **product-construct.ts** adında dosya oluşturdum. **product-construct.ts** dosyasını açıp product construct tanımlaması yaptım.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966637801/53a9af98-28cd-4448-ba8e-ada6c99e40a3.png)

modules directory

import { Construct } from "constructs";  
  
export class ProductConstruct extends Construct {  
  constructor(scope: Construct, id: string) {  
    super(scope, id);  
  }  
}

ProductConstruct oluşturduktan sonra ProductServerlessApplicationStack gelip ProductConstruct nesnesini oluşturdum.

import \* as cdk from "aws-cdk-lib";  
import { Construct } from "constructs";  
import { ProductConstruct } from "./modules";  
  
export class ProductServerlessApplicationStack extends cdk.Stack {  
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {  
    super(scope, id, props);  
  
    new ProductConstruct(this, "Products");  
  }  
}

Bu şekilde bir construct’ı stack ile bağlamış olduk. Şimdi lambda servislerimizi yazalım. İlk yazacağımız servis ürün oluşturmamızı sağlayacak. Her ürünün bir id değeri olacak ve id değerini uuid şeklinde tutacağız. Bu yüzden **uuid** npm paketini indirmemiz gerekiyor.

npm install uuid  
npm install --include\=dev @types/uuid

Yukarıdaki kodlar ile uuid ve uuid types paketlerini indirmiş olduk. Bu örnek uygulamada ürünlerimizi **dynamodb** üzerinde saklayacağız. Bu yüzden dynamodb client nesnesi oluşturabilmek, put command, update command, scan command sınıflarına erişebilmek için **dynamodb** npm paketini indirmeliyiz.

npm install @aws-sdk/client-dynamodb

Son olarak lambda sınıflarına, type tanımlamalarına erişebilmek için **aws-lambda types** npm paketini indirmeliyiz.

npm install --include\=dev @types/aws-lambda

**services** klasörü içerisine **create.ts** adında bir dosya oluşturdum. Bir ürün oluşturabilmek için ürünün başlık, açıklama ve ücret parametreleri alıyoruz.

// create.ts  
import { APIGatewayEvent, APIGatewayProxyResult, Context } from "aws-lambda";  
import {  
  AttributeValue,  
  DynamoDBClient,  
  PutItemCommand,  
} from "@aws-sdk/client-dynamodb";  
import { v4 as uuidv4 } from "uuid";  
import { ApiError } from "../../api-error";  
  
export const handler = async (  
  event: APIGatewayEvent,  
  context: Context  
): Promise<APIGatewayProxyResult\> => {  
  try {  
    const requestBodyJSON = JSON.parse(event.body ?? "{}");  
    const title = (requestBodyJSON\["title"\] ?? "") as string;  
    const description = (requestBodyJSON\["description"\] ?? "") as string;  
    const price = (requestBodyJSON\["price"\] ?? "") as string;  
  
    if (title.length === 0) {  
      throw new ApiError(400, "Title is required");  
    }  
  
    if (description.length === 0) {  
      throw new ApiError(400, "Description is required");  
    }  
  
    if (price.length === 0) {  
      throw new ApiError(400, "Price is required");  
    }  
  
    if (isNaN(Number(price))) {  
      throw new ApiError(400, "Price is not valid");  
    }  
  
    const productId = uuidv4();  
  
    const client = new DynamoDBClient({});  
  
    const newRecord: Record<string, AttributeValue\> = {  
      id: {  
        S: productId,  
      },  
      title: {  
        S: title,  
      },  
      description: {  
        S: description,  
      },  
      price: {  
        N: parseFloat(price).toFixed(2),  
      },  
      is\_deleted: {  
        BOOL: false,  
      },  
      created\_date: {  
        S: new Date().toUTCString(),  
      },  
    };  
  
    const command = new PutItemCommand({  
      TableName: "products",  
      Item: newRecord,  
    });  
  
    await client.send(command);  
  
    return {  
      statusCode: 201,  
      body: JSON.stringify({  
        data: {  
          title: title,  
          description: description,  
          price: price,  
          id: productId,  
        },  
      }),  
    };  
  } catch (error) {  
    if (error instanceof ApiError) {  
      return {  
        statusCode: error.statusCode,  
        body: JSON.stringify({  
          message: error.message,  
        }),  
      };  
    }  
    return {  
      statusCode: 500,  
      body: JSON.stringify({  
        message: "Unknown Error",  
      }),  
    };  
  }  
};

Burada kullanıcıdan gelen başlık, açıklama ve ücret parametrelerini basit bir kaç kontrolden geçirdikten sonra dynamodb’ye yeni bir kayıt eklemek için **PutItemCommand** sınıfını kullanıyoruz ve oluşturulan nesneyi **send** methoduna parametre olarak vererek yeni bir ürün oluşturuyoruz.

Sırada bir ürünü güncellemek için kullanacağımız fonksiyonu yazalım. Bunun için **services** klasörü altında **update.ts** adında bir dosya oluşturuyoruz.

//update.ts  
import { APIGatewayEvent, APIGatewayProxyResult, Context } from "aws-lambda";  
import {  
  AttributeValue,  
  DynamoDBClient,  
  UpdateItemCommand,  
} from "@aws-sdk/client-dynamodb";  
import { validate as uuidValidate } from "uuid";  
import { ApiError } from "../../api-error";  
  
export const handler = async (  
  event: APIGatewayEvent,  
  context: Context  
): Promise<APIGatewayProxyResult\> => {  
  try {  
    const pathParameters = event.pathParameters ?? {};  
    const productId = pathParameters\["id"\] ?? "";  
  
    const requestBodyJSON = JSON.parse(event.body ?? "{}");  
    const title = (requestBodyJSON\["title"\] ?? "") as string;  
    const description = (requestBodyJSON\["description"\] ?? "") as string;  
    const price = (requestBodyJSON\["price"\] ?? "") as string;  
  
    if (productId.length === 0) {  
      throw new ApiError(400, "Product Id is required");  
    }  
  
    if (!uuidValidate(productId)) {  
      throw new ApiError(400, "Product Id is not valid");  
    }  
  
    if (title.length === 0) {  
      throw new ApiError(400, "Title is required");  
    }  
  
    if (description.length === 0) {  
      throw new ApiError(400, "Description is required");  
    }  
  
    if (price.length === 0) {  
      throw new ApiError(400, "Price is required");  
    }  
  
    if (isNaN(Number(price))) {  
      throw new ApiError(400, "Price is not valid");  
    }  
  
    const client = new DynamoDBClient({});  
  
    const command = new UpdateItemCommand({  
      TableName: "products",  
      Key: {  
        id: {  
          S: productId,  
        },  
      },  
      UpdateExpression:  
        "set title = :title, description = :description, price = :price",  
      ConditionExpression: "id = :id and is\_deleted = :is\_deleted",  
      ExpressionAttributeValues: {  
        ":title": { S: title },  
        ":description": { S: description },  
        ":price": { N: parseFloat(price).toFixed(2) },  
        ":id": { S: productId },  
        ":is\_deleted": { BOOL: false },  
      },  
      ReturnValues: "ALL\_NEW",  
    });  
  
    await client.send(command);  
  
    return {  
      statusCode: 200,  
      body: JSON.stringify({  
        data: {  
          title: title,  
          description: description,  
          price: price,  
          id: productId,  
        },  
      }),  
    };  
  } catch (error) {  
    if (error instanceof ApiError) {  
      return {  
        statusCode: error.statusCode,  
        body: JSON.stringify({  
          message: error.message,  
        }),  
      };  
    }  
    return {  
      statusCode: 500,  
      body: JSON.stringify({  
        message: "Unknown Error",  
      }),  
    };  
  }  
};

Ürünü güncellemek için ürün id, başlık, açıklama, ücret parametrelerini kullanıcıdan alıyoruz. Bunları kontrollerden geçirdikten sonra ürünü güncellemek için **UpdateItemCommand** sınıfını kullanarak bir nesne oluşturuyoruz. Daha sonra **send** methodu ile ürün verisini güncelliyoruz.

Sıradaki servisimiz ürün silme fonksiyonu. **services** klasörü içerisine **delete.ts** dosyasını oluşturuyoruz.

//delete.ts  
import { APIGatewayEvent, APIGatewayProxyResult, Context } from "aws-lambda";  
import { validate as uuidValidate } from "uuid";  
import { ApiError } from "../../api-error";  
import { DynamoDBClient, UpdateItemCommand } from "@aws-sdk/client-dynamodb";  
  
export const handler = async (  
  event: APIGatewayEvent,  
  context: Context  
): Promise<APIGatewayProxyResult\> => {  
  try {  
    const pathParameters = event.pathParameters ?? {};  
    const productId = pathParameters\["id"\] ?? "";  
  
    if (productId.length === 0) {  
      throw new ApiError(400, "Product Id is required");  
    }  
  
    if (!uuidValidate(productId)) {  
      throw new ApiError(400, "Product Id is not valid");  
    }  
    const client = new DynamoDBClient({});  
  
    const command = new UpdateItemCommand({  
      TableName: "products",  
      Key: {  
        id: {  
          S: productId,  
        },  
      },  
      UpdateExpression: "set is\_deleted = :is\_deleted",  
      ConditionExpression: "id = :id and is\_deleted = :current\_is\_deleted",  
      ExpressionAttributeValues: {  
        ":id": { S: productId },  
        ":current\_is\_deleted": { BOOL: false },  
        ":is\_deleted": { BOOL: true },  
      },  
      ReturnValues: "ALL\_NEW",  
    });  
  
    await client.send(command);  
  
    return {  
      statusCode: 200,  
      body: JSON.stringify({  
        message: "Product is deleted",  
      }),  
    };  
  } catch (error) {  
    if (error instanceof ApiError) {  
      return {  
        statusCode: error.statusCode,  
        body: JSON.stringify({  
          message: error.message,  
        }),  
      };  
    }  
    return {  
      statusCode: 500,  
      body: JSON.stringify({  
        message: "Unknown Error",  
      }),  
    };  
  }  
};

Ürün silme fonksiyonu, ürün güncelleme fonksiyonu ile mantık olarak aynı. Bu örnekte ürünü veritabanından tamamen silmek yerine **is\_deleted** değerini **true** yaparak silinmiş olarak işaretliyoruz.

Son olarak ürünleri getirecek olan fonksiyonumuzu yazalım. Bunun için **services** klasörü altına **get-list.ts** dosyasını açalım.

// get-list.ts  
import { DynamoDBClient, ScanCommand } from "@aws-sdk/client-dynamodb";  
import { APIGatewayEvent, APIGatewayProxyResult, Context } from "aws-lambda";  
import { ApiError } from "../../api-error";  
  
export interface ProductDto {  
  id: string;  
  title: string;  
  description: string;  
  price: number;  
}  
  
export const handler = async (  
  event: APIGatewayEvent,  
  context: Context  
): Promise<APIGatewayProxyResult\> => {  
  try {  
    const client = new DynamoDBClient({});  
  
    const command = new ScanCommand({  
      FilterExpression: "is\_deleted = :is\_deleted",  
      ExpressionAttributeValues: {  
        ":is\_deleted": { BOOL: false },  
      },  
      TableName: "products",  
    });  
  
    const response = await client.send(command);  
  
    const productList: ProductDto\[\] = (response.Items ?? \[\]).map((p) => {  
      return {  
        id: p\["id"\].S ?? "",  
        title: p\["title"\].S ?? "",  
        description: p\["description"\].S ?? "",  
        price: parseFloat(p\["price"\].N ?? "0.0"),  
      };  
    });  
  
    return {  
      statusCode: 200,  
      body: JSON.stringify({  
        data: productList,  
      }),  
    };  
  } catch (error) {  
    if (error instanceof ApiError) {  
      return {  
        statusCode: error.statusCode,  
        body: JSON.stringify({  
          message: error.message,  
        }),  
      };  
    }  
    return {  
      statusCode: 500,  
      body: JSON.stringify({  
        message: "Unknown Error",  
      }),  
    };  
  }  
};

**ScanCommand** sınıfını dynamodb’den verileri çekmek için kullanırız. Burada **is\_deleted=false** olan ürünleri istediğimiz için filtreme yaptık ve **send** methodu ile ürünleri dynamodb’den aldık. Temel crud işlemlerini yapan fonksiyonları yazdığımıza göre artık bunları lambda olarak tanımla işlemini geçebiliriz. Bunun için **ProductConstruct** içerisine geri dönüyoruz.

import { Construct } from "constructs";  
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";  
import { Runtime } from "aws-cdk-lib/aws-lambda";  
import { join } from "path";  
import { Duration } from "aws-cdk-lib";  
import \* as apigateway from "aws-cdk-lib/aws-apigateway";  
import { AttributeType, Table } from "aws-cdk-lib/aws-dynamodb";  
import \* as cdk from "aws-cdk-lib";  
  
export class ProductConstruct extends Construct {  
  constructor(scope: Construct, id: string) {  
    super(scope, id);  
  
    const restApiId = "ProductApi";  
    const dynamoProductTableName = "products";  
  
    const dynamoProductTable = new Table(this, dynamoProductTableName, {  
      partitionKey: {  
        name: "id",  
        type: AttributeType.STRING,  
      },  
      tableName: dynamoProductTableName,  
      removalPolicy: cdk.RemovalPolicy.RETAIN,  
    });  
  
    const productApiGateaway = new apigateway.RestApi(this, restApiId, {  
      restApiName: "Product Api",  
      description: "Product Rest Api",  
      deploy: true,  
      deployOptions: {  
        stageName: "prod",  
        tracingEnabled: true,  
      },  
      endpointTypes: \[apigateway.EndpointType.EDGE\],  
    });  
  
    const createProductFunction = new NodejsFunction(this, "CreateProduct", {  
      runtime: Runtime.NODEJS\_18\_X,  
      entry: join(\_\_dirname, "services", "create.ts"),  
      timeout: Duration.minutes(1),  
      memorySize: 512,  
    });  
  
    const putProductFunction = new NodejsFunction(this, "PutProduct", {  
      runtime: Runtime.NODEJS\_18\_X,  
      entry: join(\_\_dirname, "services", "update.ts"),  
      timeout: Duration.minutes(1),  
      memorySize: 512,  
    });  
  
    const deleteProductFunction = new NodejsFunction(this, "DeleteProduct", {  
      runtime: Runtime.NODEJS\_18\_X,  
      entry: join(\_\_dirname, "services", "delete.ts"),  
      timeout: Duration.minutes(1),  
      memorySize: 512,  
    });  
  
    const getProductListFunction = new NodejsFunction(this, "getProductList", {  
      runtime: Runtime.NODEJS\_18\_X,  
      entry: join(\_\_dirname, "services", "get-list.ts"),  
      timeout: Duration.minutes(1),  
      memorySize: 512,  
    });  
  
    dynamoProductTable.grantReadWriteData(createProductFunction);  
    dynamoProductTable.grantReadData(getProductListFunction);  
    dynamoProductTable.grantReadWriteData(putProductFunction);  
    dynamoProductTable.grantReadWriteData(deleteProductFunction);  
  
    const getProductListFunctionIntegration = new apigateway.LambdaIntegration(  
      getProductListFunction  
    );  
  
    const createProductFunctionIntegration = new apigateway.LambdaIntegration(  
      createProductFunction  
    );  
  
    const putProductFunctionIntegration = new apigateway.LambdaIntegration(  
      putProductFunction  
    );  
  
    const deleteProductFunctionIntegration = new apigateway.LambdaIntegration(  
      deleteProductFunction  
    );  
  
    const productResource = productApiGateaway.root.addResource("products", {});  
    const productResourceWithId = productResource.addResource("{id}");  
  
    productResource.addMethod("GET", getProductListFunctionIntegration);  
  
    productResource.addMethod("POST", createProductFunctionIntegration);  
  
    productResourceWithId.addMethod("PUT", putProductFunctionIntegration);  
  
    productResourceWithId.addMethod("DELETE", deleteProductFunctionIntegration);  
  }  
}

İlk önce bir **dynamodb tablosuna** ihtiyacımız olduğu için construct içerisinde tablo oluşturduk. **partitionKey** basit bir **primary key’dir.** Bu gerekli bir parametredir. ismini ve tipini belirttim. **tableName** tablonun adıdır. **RemovalPolicy** dynamodb tablosunun CloudFormation tarafından yönetilmesinin durdurulması durumunda tablonun durumunun ne olacağını kontrol eder. 3 farklı durum vardır:

1.  Destroy: Tabloyu yok et.
2.  Retain: Tabloyu tutmaya devam et.
3.  Snapshot: Tabloyu yok et, ancak yok etmeden önce bir kopyasını al ve onu sakla.

Bir çok kaynakta **Retain** default seçenektir.

const dynamoProductTableName = "products";  
  
const dynamoProductTable = new Table(this, dynamoProductTableName, {  
  partitionKey: {  
    name: "id",  
    type: AttributeType.STRING,  
  },  
  tableName: dynamoProductTableName,  
  removalPolicy: cdk.RemovalPolicy.RETAIN,  
});

Daha sonra bir **apigateaway** kaynağına ihtiyacımız var. Kullanıcıyı bir ürün oluşturmak, güncellemek, silmek ya da ürünleri getirmek istediğinde **apigateaway** ile karşılayacağız. **apigateaway** ile bir **RestApi** oluşturduk ve deploy değerini **true** yaptık. Bu şekilde rest api direk dış dünyaya açmış olduk. **deployOptions** ile **stageName** ve **tracingEnabled** değerlerimizi verdik. **tracingEnabled** değerine **true** vererek Amazon X-Ray izlemeyi aktifleştirdik. **endpointTypes** değerine ise **apigateway.EndpointType.EDGE** değerini verdik. Bu restApi’lar için varsayılan bir değerdir. **EDGE** dışında **REGIONAL** ve **PRIVATE** değerler vardır.

const restApiId = "ProductApi";  
const productApiGateaway = new apigateway.RestApi(this, restApiId, {  
  restApiName: "Product Api",  
  description: "Product Rest Api",  
  deploy: true,  
  deployOptions: {  
    stageName: "prod",  
    tracingEnabled: true,  
  },  
  endpointTypes: \[apigateway.EndpointType.EDGE\],  
});

Daha sonra **NodeJs lambda fonksiyonlarını** tanımladık. lambda fonksiyonlarımın çalışacağı **runtime** versiyonunu **nodejs18** seçtik. **timeout** olarak **1** **dakika** verdik. lambda fonksiyonun kullanacağı **memorySize** değerini **512mb** olarak belirledik. Son olarak **entrye** değer olarak lambda fonksiyonun çalıştıracağı handler fonksiyonlarını verdik.

const createProductFunction = new NodejsFunction(this, "CreateProduct", {  
    runtime: Runtime.NODEJS\_18\_X,  
    entry: join(\_\_dirname, "services", "create.ts"),  
    timeout: Duration.minutes(1),  
    memorySize: 512,  
  });  
  
  const putProductFunction = new NodejsFunction(this, "PutProduct", {  
    runtime: Runtime.NODEJS\_18\_X,  
    entry: join(\_\_dirname, "services", "update.ts"),  
    timeout: Duration.minutes(1),  
    memorySize: 512,  
  });  
  
  const deleteProductFunction = new NodejsFunction(this, "DeleteProduct", {  
    runtime: Runtime.NODEJS\_18\_X,  
    entry: join(\_\_dirname, "services", "delete.ts"),  
    timeout: Duration.minutes(1),  
    memorySize: 512,  
  });  
  
  const getProductListFunction = new NodejsFunction(this, "getProductList", {  
    runtime: Runtime.NODEJS\_18\_X,  
    entry: join(\_\_dirname, "services", "get-list.ts"),  
    timeout: Duration.minutes(1),  
    memorySize: 512,  
  });

Bu lambda fonksiyonların dynamodb ile etkileşime geçebilmesi için gerekli izinleri lambda servislerine vermemiz gerekiyor. Create, update, delete servislerine **okuma(read)** ve **yazma(write)** **izinlerini,** get list servisine ise sadece **okuma (read) iznini** verdik.

dynamoProductTable.grantReadWriteData(createProductFunction);  
dynamoProductTable.grantReadData(getProductListFunction);  
dynamoProductTable.grantReadWriteData(putProductFunction);  
dynamoProductTable.grantReadWriteData(deleteProductFunction);

Oluşturduğumuz apigateaway ile lambda fonksiyonları entegre etmemiz gerekiyor.

const getProductListFunctionIntegration = new apigateway.LambdaIntegration(  
  getProductListFunction  
);  
  
const createProductFunctionIntegration = new apigateway.LambdaIntegration(  
  createProductFunction  
);  
  
const putProductFunctionIntegration = new apigateway.LambdaIntegration(  
  putProductFunction  
);  
  
const deleteProductFunctionIntegration = new apigateway.LambdaIntegration(  
  deleteProductFunction  
);

Lambda fonksiyonlarını apigateaway ile entegre ettikten sonra ise api kaynakları, methodları tanımlamamız gerekiyor. Bu şekilde apigateaway ile müşterimizinden istekleri alıp, gerekli lambda fonksiyonlarını çalıştırabileceğiz. Lambda fonksiyonlarımız dynamodb ile beraber ürün ekleyebilecek, güncelleyebilecek, silebilecek ve tüm ürünleri listeleyebilecek.

const productResource = productApiGateaway.root.addResource("products", {});  
const productResourceWithId = productResource.addResource("{id}");  
  
productResource.addMethod("GET", getProductListFunctionIntegration);  
  
productResource.addMethod("POST", createProductFunctionIntegration);  
  
productResourceWithId.addMethod("PUT", putProductFunctionIntegration);  
  
productResourceWithId.addMethod("DELETE", deleteProductFunctionIntegration);

Sıradaki adım uygulamamızı **cloudformation** ile birlikte tüm kaynakları oluşturmak ve çalışabilir hale getirmek olacaktır. Bunu yapmak için önce uygulamamızın **cloudformation** tarafından anlaşılıp, çalıştırabilmesi için bir şablon oluşturmamız gerekiyor. Aşağıda görmekte olduğunuz kod parçası uygulamamızı çalıştırır ve uygulamamızdaki bulunan tüm kaynakları içeren bir **cloudformation şablonuna** çevirir. Oluşturulan şablon dosyası **cdk.out** klasörü içerisinde bulunur. Yine yazmış olduğumuz crud fonksiyonlarını da burada esbuild tarafından javascripte dönüşmüş hallerini bulabilirsiniz.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966639705/e48ccf78-c3c7-484d-bf30-6c040650bda5.png)

cdk synth

Dosya format türü yaml’dır. **cdk synth** komutu çalıştığında **esbuild** ile nodejs lambda fonksiyonlarımızı javascript koduna dönüştürmek için **dockera** ihtiyaç duyacaktır. Eğer docker makinenizde yoksa ya da çalışmıyorsa aşağıdaki gibi bir hata alabilirsiniz.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966642166/083ef357-9c89-4969-9098-b228b59a46d7.png)

docker error

Cloudformation şablonunu oluşturduktan sonra bunu deploy edebiliriz. Deploy edebilmemiz için **cdk deploy** komutunu kullanabiliriz. Bu işlemi yapabilmemiz için bir aws hesabına ihtiyacımız olacaktır. aws hesabınıza giriş yaptıktan sonra **IAM Identity Center** panelinden kullanıcılar ekranına gelip bir kullanıcı oluşturacağız.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966644331/ecdba60f-acad-41ea-a869-89f955c76d59.png)

Add user butonuna tıklayalım.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966646384/6bbcfb18-1f9c-4992-a1c7-729e5212c299.png)

Gerekli bilgileri girdikten sonra oluşturduğumuzun kullanıcının email adresine kurulum bilgilerini içeren bir email alacaktır. Bu email üzerinden şifresini oluşturabilecek ve aws paneline erişim sağlayabilecek.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966648578/2cd2629c-3714-4313-8c2a-929868b455dc.png)

Bu işlemleri yaptıktan sonra kullacımızın paneli yukarıdaki ekran alıntısındaki gibi gözükecektir. AWS kaynaklarına erişim sağlayabilmesi için AWS account’u ile kullanıcıyı eşleştirmemiz gerekiyor.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966650490/d9da09e6-1461-4f6a-b59e-d96ad8a40fa1.png)

AWS Accounts

Aws accounts sekmesine geliyoruz ve root altındaki AWS tıklıyoruz.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966652654/023b0e7b-a7a5-47b1-aa3d-182f904f73dc.png)

AWS Account Detail

**Assign users or groups** butonuna tıkladıktan sonra açılan ekranda oluşturduğumuz kullanıcıyı ve izin kümesini seçip süreci tamamlıyoruz. Eğer sistemde bir izin kümesi yoksa onu da **permission sets** sekmesinden oluşturabilirsiniz. Bu işlemi yaptıktan sonra kullanıcınız verilen izinler doğrultusunda kaynaklara erişim sağlayabilecektir.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966654632/3b449fbe-1cb1-4b1e-a721-f0c6f75e05fb.png)

Bu işlemlerden sonra konsol üzerinden **aws configure sso** komutunu çağırarak kullanıcımızın profil bilgisini bilgisayarımız üzerinde oluşturmalıyız. Bizden birkaç bilgi istedikten sonra profilimiz başarılı bir şekilde oluşturmuş olduk.

aws configure sso

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966656109/83012cf7-e4b2-40c3-b312-046c3f3238cc.png)

Son olarak profilimiz ile deployment sürecini başlatmalıyız. Bunun için **aws deploy —profile \[profile name\]** komutunu çalıştırmalıyız.

aws deploy --profile \[profile name\]

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966658409/2c54eda5-6e8a-44f7-8d7f-b2601869b35f.png)

deployment process

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966660200/d3f8a829-02aa-4eac-a15f-0bd0f629c1e8.png)

deploymen result

Deployment işlemi bittikten sonra bize api endpoint url bilgisini döndürdüğünü görebiliriz. Panelden cloudformation ekranına geldiğimizde yeni bir stack oluştuğunu görebiliriz.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966661716/218d43b0-24f4-4cc4-b583-2785ff66fe58.png)

cloudformation

dynamodb tables ekranına geldiğimizde **products** isminde yeni bir dynamodb tablosu oluşturulduğunu görebiliriz.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966663309/9dfbc121-7c29-47dc-add5-1b6f04fcdbf7.png)

dynamodb tables

apigateaway ekranına geldiğimizde yeni bir rest api oluşturulduğunu görebiliriz.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966665405/92473213-54a3-4b87-8c0b-976f646c521f.png)

api gateaway

lambda ekranına geldiğimizde de lambda fonskiyonlarını görebiliriz.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966667180/75cbb00d-a9c6-4923-93f0-339d216f7f09.png)

lambda functions

Herşey doğru bir şekilde oluşturulduğunu gördüğümüze göre beraber doğru bir şekilde çalıştıklarını test edelim.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966669353/1fc35d3a-8b86-4326-b79d-a80a6aeaf434.png)

create product

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966671328/4dce3892-6668-481a-a094-a799a2e3e33a.png)

update product

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966673481/02a9f66b-99ce-48a7-8b96-8fc4db67acbf.png)

get product list

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1710966675622/1368b462-eff1-4465-a0c2-38b0368653f4.png)

delete product

postman üzerinden yaptığımız testlerde rest api servislerimiz doğru şekilde çalıştığını görüyoruz. Eğer oluşturduğunuz stackleri yok etmek istiyorsanız cdk destroy — profile \[profile name\] komutunu çalıştırarak yok edebilirsiniz.

cdk destroy --profile \[profile name\]

Bu yazımızda aws cdk ile lambda fonksiyonları, dynamodb ve apigateaway birbirleri ile entegre şekilde çalıştırabildik. Tüm bu süreci tamamen infrastructure kod ile yaptık. Sizde istediğiniz aws kaynaklarını aws cdk ile oluşturabilirsiniz. Umarım sana dokunmuş olabilirim. Bu örnek projenin kaynak kodunu aşağıya bırakıyorum. Merak ettiğin bir şey olursa benimle iletişime geçebilirsin.

[**GitHub - serhatleventyavas/ProductServerlessApplication**  
*Contribute to serhatleventyavas/ProductServerlessApplication development by creating an account on GitHub.*github.com](https://github.com/serhatleventyavas/ProductServerlessApplication "https://github.com/serhatleventyavas/ProductServerlessApplication")[](https://github.com/serhatleventyavas/ProductServerlessApplication)
