Aws Amplify Kullanımı-2

Hatice Edis
10 min readFeb 21, 2021

--

Herkese Tekrar Selamlar 🖐 Bu yazı, bir önceki Amplify yazısının devamı olacak :) Bu yazıda bahsedeceğim konular şöyle olacak :

  • Amplify ile GraphQL modelleri oluşturma
  • Arayüzde aws-amplify paketi ile Query ve Mutation’ları yönetmek

Not: GraphQL hakkında bilginiz yoksa, çok önceden yazmış olduğum bu yazıyı da tavsiye ederim :)

O zaman haydi başlayalım :)

Amplify daha önce de söylediğim gibi, veri tabanı olarak DynamoDB kullanıyor. DynamoDB , NoSQL bir veritabanı tabanıdır. Dolayısıyla şemalar ile kurgulanır. Amplify bu kurguyu sağlamak için bize bazı directive’ler vermiş. Bu directive’ler:

@model:

DynamoDB’de tutulacak objenin hangi özelliklerle tutulacağını belirmemizde yardımcı olur. Objedeki fieldleri ve özelliklerini burada tanımlarız. Amplify’ın oluşturduğu schema.graphql dosyasına bakarsak bu dosyanın yapısı şöyledir. İlk önce type belirlememiz gerekir. Bu type ile Model, Query veya Mutation olduğunu belirtiyoruz. Model olduğunu belirtmek için type modeladı @model ile objeyi yazıyoruz. Burada modeladı, DynomaDB’deki tablo adına denk gelmektedir. Daha sonra tablodaki alanları yani objenin propertylerini tanımlamamız gerekir. Bu da alanadi: değişkentipi şeklinde tanımlanır. Ayrıca GraphQL şemasındaki verileceği gibi değişkentipinin sonuna zorunlu olduğu belirtmek için ! işareti konulabilir. Dolayısıyla ortaya şöyle bir formül çıkıyor genel olarak:

type modeladi @model {
alan1: değişkentipi
alan2: değişkentipi
...
}

Örnek verecek olursak, todo adında bir tablomuz olsun. Ve id, name, description ve done alanlarından oluşsun. id, name alanları zorunlu olsun. type şemamız şöyle olacaktır.

type Todo @model {
id: ID!
name: String!
description: String
done: Boolean
}

Bu arada id alanını ID olarak belirtiğimiz için create ederken inputta gönderilmediğinde bile otomatik olarak oluşturulur ve unique keydir. Peki Amplify da veri tipleri vardır.

  • ID: Obje için tanımlayıcı keydir. Benzersizdir.
  • String: UTF-8 karakter serisinden oluşan metin veri tipidir.
  • Int: -(2^31) and 2^32–1 arasındaki tam sayılardan oluşan bir veri tipidir.
  • Float: IEEE 754 standartıyla var olan bir ondalıklı sayıdır. (IEEE 754 için bakınız )
  • Boolean: true veya false değer olan değişken tipidir.
  • AWSDate: ISO_8601 dan oluşturulmuş ve YYYY-MM-DD formatında olan tarih tipidir. (bakınız)
  • AWSTime: ISO_8601 dan oluşturulmuş ve hh:mm:ss.sss formatında olan zaman tipidir. (bakınız)
  • AWSDateTime: ISO_8601 dan oluşturulmuş zaman tipidir ve YYYY-MM-DDThh:mm:ss.sssZ formatına sahiptir. (bakınız)

Modele verdiğiniz alanlar dışında AWSDateTime tipinde createdAt ve updatedAt adında iki alan daha modele otomatik olarak eklenir. Bunlar tahmin edeceğiniz gibi oluşturulma ve son güncelleme tarihleridir.

Eğer bir alanı array şekilde tanımlamak istersek [değişkentipi!] şeklinde tanımlamamız yeterli olacaktır.

@key :

@key directive i, veri tabanında performans için yapılan indexleme için kullanılır. Sıralama, arama gibi veri tabanı işlemlerinin verimli bir şekilde yapılmasını sağlar. Genel formülü şu şekildedir.

@key(name: String, fields: [String!]!, queryField: String)
  • name: İndexleme anahtarıdır. Hangi field a göre indexleme yapılacağını belirler.
  • fields: Bu alan bir arraydir. İlk elemanı larşılaştırma yapılacak alanın adıdır, ikinci anahtar ise sıralama yapılacak alandır. Eğer [1, 2,.. n] şeklinde 2 den fazla alan belirtsek, tüm alanların kombinasyonu olarak tek bir sıralama oluşturulur.
  • queryField: Hangi query de bu indexleme ve sorgu işlemlerini aktif olacağı belirlenir.

Örneğin todo modelini oluşturma tarihine göre sıralama yapmasını istersek, indexleme yapılması için sortedIndex adında bir oluşturalım. name ve fields ın ilk elemanı olarak bu alanı verelim. Amplify ın otomatik oluşturacağı listTodos query’si dışında bir query oluşturulmasını bu query’de sort işleminin uygulaması isteyelim. Bu query’e todosByDate adını verelim. Type’ı Todo olan modelimizin şeması aşağıdaki gibi olur.

type Todo @model @key(
name: "sortedIndex",
fields: ["sortedIndex", "createdAt"],
queryField: "todosByDate") {
id: ID!
name: String!
description: String
done: Boolean
createdAt: AWSDateTime!
sortedIndex: String!
}

create mutation’ınında bu oluşturulduğumuz sortedIndex adındaki alanı da göndermemiz lazımdır.

Örneğin:

mutation {
createTodo(input: {
name: "Görev 1",
description: "deneme 1",
done: false,
sortedIndex: "sortedIndex"
}) {
id
name
description
}
}

Verimizi buradaki gibi oluşturduğumuzda,

2 tane query ‘yi karşılaştıralım.

Burada todosByDate querysinden gelen verinin oluşturma tarihine göre yeniden eskiye doğru sıralı halde geldiği görülürken, listTodos query sinde herhangi bir sıralama yoktur.

Uyarı : Eğer modelimizde önceden bir indexleme varsa ve biz indexlemesi için name’e farklı bir field verirsekFailed to start API Mock endpoint LimitExceededException: Subscriber limit exceeded: Only 1 online index can be created or deleted simultaneously per tableşeklinde bir hata alırız. Çünkü var olan verilerde bir indexleme işlemi yapılmıştır. Verileri sıfırladığımızda ancak bu hata gider. Bu yüzden dikkat edilmesi gerekmektedir.Ya da eski indexleme alanını kullanmaya devam etmeliyiz.

@connection:

@connection ile model typeları arasındaki bağlantının kurulması sağlanır.

Kullanımı şu şekildedir.

@connection(keyName: String, fields: [String!]) 
  • keyName: Bağlantı kurulacak modelin adıdır.
  • fields: Modeldeki hangi fieldın bağlantı kurulacak model ile bağlantılı olacağını söylememizi sağlar.

Örnek verecek olursak, ilk yazdığımız Todo type ı ele alalım. Ve bu todoların bir de tipi olsun.

type TodoTypes @model {
id: ID!
name: String!
}
type Todo @model {
id: ID!
name: String!
description: String
done: Boolean
}

Burada Todo type a typeID adında ve string tipinde zorunlu alan daha ekleyelim. Bağlı olduğu todoType ın bilgilerini getirmesi için model’de TodosType modeli bile bağlantılı olduğunu ve burada todoType id si ile hangi alanın eşleşeceğini söylememiz gerekir. Bizim bu alanımız typeID olsun.

Sonuç olarak Todo modelimiz bu şekilde oluşturur.

type Todo @model {
id: ID!
name: String!
description: String
done: Boolean
typeID: String!
type: TodoType @connection(fields: ["typeID"])
}

@function :

Amplify içinde Lambda functionlar da ekleyebiliyoruz. Bu lambda functionları Query ve Mutation olarak çağırmamızı sağlayan bir directive’dir. Genel kullanımı şu şekildedir.

@function(name: String!, region: String) 
  • name: Lambda function adı.
  • region: Bölge

Örneğin; CalcuteProductDiscount adında bir Lambda function yazılmış olsun. Ve Product adında bir modelimiz olsun.

type Production @model {
id: ID!
name: String!
amount: String!
}

Bu CalcuteProductDiscount adındaki function, bir ürünün indirimli halini hesaplıyor olsun. Query de bir alanda bu function ı çağırıp sonucunu getirelim. Bu alanın adı discountedAmount olsun. Bu alanın Float bir değer olduğunu ve yazdığımız lambda function’ınında geldiğini belirtirsek.

type Production @model {
id: ID!
name: String!
amount: String!
discountedAmount: Float @function(name"CalcuteProductDiscount")
}

Production modelimiz şeması bu şekilde olur. Ve yazacağımız Query de bu function ı çağırmamız gerekir.

type Query { 
productions: [Production] @function(name:"CalcuteProductDiscount")
}

Burada type Query diyerek yeni bir Query oluşturduk. Sonucunda productions adında bir veri dönmesini istedik ve bu veride dönen modelin Production modeline bağlı olduğu söyledik. Ek olarak da CalcuteProductDiscount adındaki lambda function’ı çağırmasını söyledik.

Not: amplify add function diyerek amplify için yeni bir function oluşturabilir. Bunu daha detaylı şekilde ilerideki yazılarda anlatmayı düşünüyorum. Bu functionları kullanmak için amplify push diyerek deploy etmemiz gerekir.

@searchable :

@searchable directive ‘i @model içindeki objenin, Amazon Elasticsearch ile yönetilmesini ve queryler işlerinde Elasticsearch çözümünün kullanılmasını sağlar. Kullanımı ise modele searchable olduğu belirtlememiz yeterli olacaktır. Örneğin:

type Production @model @searchable{
id: ID!
name: String!
amount: String!
}

Not: @searchable directive’ini kullanabilmemiz için, Elasticsearch aktif olacağında deploy etmiş olmamız gerekmektedir. amplify mock ile çalışmaz.

@http :

@http directive’i dışarıdan bir servise bağlanmamızı sağlar.

Şöyle bir yapısı vardır.

enum HttpMethod { PUT POST GET DELETE PATCH }
input HttpHeader { key: String value: String }
@http(method: HttpMethod, url: String!, headers: [HttpHeader])
  • method olarak HTTP request methodları olarak bildiğimiz GET POST PUT PATCH DELETE methodlarından birini alır. Default ı GET idir.
  • url olarak, bağlanılacak servis in domain adresi yazılır. (HTTP ve HTTPS her ikisini de kabul eder.)
  • headers olarak, ise bir array değeri alır. Zorunlu değildir. Bu array içinde ise her eleman key ve value özelliklerine sahip objeden oluşur. key headersdaki istenen değerini adı, value ise o değerin kendisidir. Örneğin headers: [{key: ‘Token’}, {value: ‘####’}] gibi.

Bir örnek verirsek:

Elimizdeki postları alacağımız bir servis olsun:

url’i: https://www.example.com/posts

ve get methodu ile alalım.

type Query { 
listPosts: Post
@http(
url: "https://www.example.com/posts"
method: GET
}

bu şekilde alabiliriz. Tabii burada method parametresini hiç vermeyebilirdik. Çünkü default değeri GET .

Ya da bu postları update edeceğimiz bir mutation yazalım.

POST verisini update etmek için bir

https://www.example.com/dev/posts/:id

bu adrese bir PUT isteği atalım. headers ında Token istesin.

type Post {
id: ID!
title: String
description: String
views: Int
}
type Mutation { updatePost( title: String! description: String! views: Int ): Post @http( method: PUT url: "https://www.example.com/dev/posts/:id" headers: [{ key: "Token", value: "##########" }])
}

şeklinde yazabiliriz.

@predictions :

@predictions directive ‘i Amazon Rekognition, Amazon Translate , Amazon Polly gibi AI / Machine Learning hizmetlerinin kullanılmasını sağlar.

Kullanımı şu şekildedir.

@predictions(actions: [PredictionsActions!]!)

Peki PredictionsActions nedir ? O da bir enumdır. Ve aşağıdaki eğerleri alır.

enum PredictionsActions { 
identifyText
identifyLabels
convertTextToSpeech
translateText
}
  • identifyText: Metni algılamak için Amazon Rekognition kullanır.
  • identifyLabels : Labelları algılamak için Amazon Rekognition kullanır.
  • convertTextToSpeech: Amazon Polly servisini kullanan bir Lambda function dır.Sonucunda ise metni konuşma olarak sentezleyip önceden hazırlanmış bir url de döner.
  • translateText: metni kaynaktan verilen dile çevirmek için Amazon Translate’i kullanır.

Bir Query yazarsak adı. speakTranslatedImageText olsun. Aşağıdaki gibi tanımlanabilir.

type Query {  speakTranslatedImageText: String @predictions(actions: [     identifyText
translateText
convertTextToSpeech
])
}

Parametre olarak ise örnekteki değerleri alır.

query SpeakTranslatedImageText($input: SpeakTranslatedImageTextInput!) {  speakTranslatedImageText(input: {   identifyText: {
key: String
}
translateText: {
sourceLanguage: from language ios code
targetLanguage: to language ios code
} convertTextToSpeech: {
voiceID: AWS VOİCE ID
}
})}

AWS Voice ID tanımları : https://docs.aws.amazon.com/polly/latest/dg/voicelist.html

Aws Dil tanımları :

@versioned

@versioned directive’i bir nesne üzerinde versiyon oluşturmak için veya model üzerindeki çakışmaları çözmek için kullanılır. Bu özellik AppSync içinde otomatik olarak var olduğu için DataStore’dan yararlanırken bu directive in kullanılmaması gerektiği unutmamalıyız.

Kullanımı ise şu şekildedir.

@versioned(versionField: String = "version", versionInput: String = "expectedVersion")
  • versionField: Modelde versiyonun tutulacağı alanın adıdır. Default değeri version dur.
  • versionInput: Mutationda versiyon karşılaştırması için alınacak input parametresinin adıdır. Default değeri expectedVersion`dur.

Örneğin;

type Post @model @versioned {
id: ID!
title: String!
}

şeklinde bir şema yazdığımızda version alanı biz tanımlamasak dahi, objeye otomatik tanımlar.

type Post @model @versioned {
id: ID!
title: String!
version: Int!
}

şema bu hali alır.

@auth

@auth directive model içindeki alan, query veya mutationlar için yetkilendirme yapmamızı sağlayan bir directive dir. Bu directive’in kullanımı çok geniş kapsamlı olduğundan bir sonraki yazıda bundan bahsedeceğim :)

Not: Örnekler ve bilgiler https://docs.amplify.aws/ adresinden edinerek alınmıştır.

Evet, modellerimiz ayarladık, diyelim ki servis tarafı artık tamam. Peki arayüzde bunu nasıl kullanacağız ? :)

Örnek olarak create-next-app ile oluşturulmuş bir react uygulamasını ele alalım. İlk olarak aws-amplify paketini projeye dahil edip, bağımlılıklara eklemek gerekiyor.

yarn add aws-amplifyornpm install aws-amplify --save

Daha sonra (bir önceki yazıdaki örnekten src dizinine aktarmış projenin genel yapısını, oradan ilerlesek) src/pages altındaki _app.js dosyasında amplify configrasyonunun yapılması gerekiyor. Bu configrasyonlar amplify tarafından otomatik generate edilen aws-exports.js dosyasında bulunuyor.

_app.js dosyasına aws-amplify ‘ı ve configrasyon dosyası olan aws-exports dosyasını import ettikten sonra

Amplify.configure(config)

şeklinde configrasyonu yapmış oluyoruz.

import Amplify from "aws-amplify";import configure from "./../aws-exports";Amplify.configure(configure)

Evet artık uygulamamızın amplify uygulaması ile bağlantısı olduğu söyledik. Dahası hangi uygulama ile bağlantısı var artık kendisi biliyor.

Örnek olarak benim bu şekilde basit bir şemam olsun.

Todoları Amplify servisin nasıl alacağımıza bakalım.

İlk olarak src/index.js dosyasındaki çoğu şeyi sildim. Sadece şu satırlar kaldı.

Daha sonra aws-amplify içindeki {API } classını import ediyorum.

import { API } from ‘aws-amplify’

Şimdi API class ı içinde birçok method barındırıyor. Biz bu methodlar içinden graphQL methodunu kullanacağız. Bu method üç tane parametre alıyor.

  • query: Çalıştırılacak query veya mutation
  • authMode: Çalışan işlemin eğer birden çok yetkilendirme türü varsa bunun hangi türü kullanacağını belirtmek için kullanılır. Bunu yetkilendirme işlemlerinde ele alacağız :)
  • variables: input vb. variablelerın gönderilmesinde kullanılır.

O zaman bize şimdi todoları listeyen query lazım. Bunu da Amplify otomatik oluşturmuştu. src/graphql/queries.js dosyasında görebiliriz. Biz buradaki ListTodos adındaki query i kullanacağız. (Çünkü tek bir todo yu değil tüm listeyi almak istiyoruz.)

O zaman index.js dosyasında bu query’yi de import edelim.

import { listTodos } from '../graphql/queries'

Şimdi ise, Home fonksiyonunda fetchTodos adında bir fonksiyon oluşturalım. Bu şöyle bir method olacak.

async function fetchTodos () {  try {    const data = await API.graphql({     query: listTodos   })   console.log(data)  } catch (err) { console.log('error fetching', err) }}

Burada şunu yaptık. Bir hata alma ihtimalimize karşı try catch blogunu kullandık. Daha sonra, API.graphql diyerek yukarı bahsettiğim API class ının graphql methodunu çağırdık. Daha sonra ise, bu method a query parametresi olarak import ettiğimiz listTodos u verdik.

Şimdi ise, useEffect ile sayfa oluştuğunda bu fonksiyonun çalışmasını sağlayalım.

Evett, verilerimiz geldiği gördük :)

Artık useState ile state imizi oluşturabilir, UI ‘yı çizebiliriz.

Tıpkı Query’leri kullandığımız gibi, mutation’ları kullanabiliriz.

Örneğin, buradaki createTodo mutation’ını kullanmak istersek, aynı şekilde otomatik oluşturulan src/graphql/mutations.js dosyasında createTodo mutation’ını import ettikten sonra kullanabiliriz. Burada mutation için göndereceğimiz input ‘u variables parametresi ile gönderebiliyoruz.

async function newTodo() {  try {    const data = await API.graphql({
query: createTodo,
variables: {
input: {
name: 'Arayüz denemesi',
description: 'Yeni deneme',
done: false
}
}
})
console.log(data)
} catch (err) { console.log('mutation error', err) }}

Evett, şimdilik bu kadar :)

Bir sonraki yazıda, mutationlarla ilgili örneklere bakacağız ve authentication yapısından, amplify admin ui dan bahsedeceğim.

Sürç-i Klavye ettiysek affola :)

--

--