1. Genel Bakış
Bu codelab'in amacı "sunucusuz" deneyim sahibi olmaktır Google Cloud Platform tarafından sunulan hizmetler:
- Cloud Functions: Çeşitli etkinliklere (Pub/Sub mesajları, Cloud Storage'daki yeni dosyalar, HTTP istekleri vb.) tepki veren işlevler şeklinde küçük iş mantığı birimleri dağıtmak için
- App Engine: Hızlı ölçeklendirme yapma ve azaltma özelliklerine sahip web uygulamalarını, web API'lerini, mobil arka uçları, statik öğeleri dağıtmak ve sunmak için
- Cloud Run: Herhangi bir dil, çalışma zamanı veya kitaplık içerebilen container'ları dağıtmak ve ölçeklendirmek için
Web ve REST API'lerini dağıtıp ölçeklendirirken bu sunucusuz hizmetlerden nasıl yararlanacağınızı, aynı zamanda bazı iyi RESTful tasarım ilkelerini nasıl kullanacağınızı keşfedin.
Bu atölyede şunlardan oluşan bir kitap rafı gezgini oluşturacağız:
- Bir Cloud Functions işlevi: Kitaplığımızdaki kitaplardan oluşan ilk veri kümesini Cloud Firestore belge veritabanına aktarmak için
- Cloud Run container'ı: Veritabanımızın içeriği üzerinde REST API açığa çıkarır.
- App Engine web ön ucu: REST API'mizi çağırarak kitap listesine göz atmak için.
Bu codelab'in sonunda web ön ucu aşağıdaki gibi görünecek:
Neler öğreneceksiniz?
- Cloud Functions
- Cloud Firestore
- Cloud Run
- App Engine
2. Kurulum ve şartlar
Kendi hızınızda ortam kurulumu
- Google Cloud Console'da oturum açıp yeni bir proje oluşturun veya mevcut bir projeyi yeniden kullanın. Gmail veya Google Workspace hesabınız yoksa hesap oluşturmanız gerekir.
- Proje adı, bu projenin katılımcıları için görünen addır. Google API'leri tarafından kullanılmayan bir karakter dizesidir. İstediğiniz zaman güncelleyebilirsiniz.
- Proje Kimliği, tüm Google Cloud projelerinde benzersizdir ve değiştirilemez (belirlendikten sonra değiştirilemez). Cloud Console, otomatik olarak benzersiz bir dize oluşturur. bunun ne olduğunu umursamıyorsunuz. Çoğu codelab'de proje kimliğinizi (genellikle
PROJECT_ID
olarak tanımlanır) belirtmeniz gerekir. Oluşturulan kimliği beğenmezseniz rastgele bir kimlik daha oluşturabilirsiniz. Alternatif olarak, kendi ölçümünüzü deneyip mevcut olup olmadığına bakabilirsiniz. Bu adımdan sonra değiştirilemez ve proje süresince kalır. - Bilginiz olması açısından, bazı API'lerin kullandığı üçüncü bir değer, yani Proje Numarası daha vardır. Bu değerlerin üçü hakkında daha fazla bilgiyi belgelerde bulabilirsiniz.
- Sonraki adımda, Cloud kaynaklarını/API'lerini kullanmak için Cloud Console'da faturalandırmayı etkinleştirmeniz gerekir. Bu codelab'i çalıştırmanın maliyeti, yüksek değildir. Bu eğitim dışında faturalandırmanın tekrarlanmasını önlemek amacıyla kaynakları kapatmak için oluşturduğunuz kaynakları silebilir veya projeyi silebilirsiniz. Yeni Google Cloud kullanıcıları 300 ABD doları değerindeki ücretsiz denemeden yararlanabilir.
Cloud Shell'i başlatma
Google Cloud dizüstü bilgisayarınızdan uzaktan çalıştırılabilse de bu codelab'de, Cloud'da çalışan bir komut satırı ortamı olan Google Cloud Shell'i kullanacaksınız.
Google Cloud Console'da, sağ üstteki araç çubuğunda bulunan Cloud Shell simgesini tıklayın:
Ortamı sağlamak ve bağlamak yalnızca birkaç dakika sürer. Tamamlandığında şuna benzer bir sonuç görmeniz gerekir:
İhtiyacınız olan tüm geliştirme araçlarını bu sanal makinede bulabilirsiniz. 5 GB boyutunda kalıcı bir ana dizin sunar ve Google Cloud üzerinde çalışarak ağ performansını ve kimlik doğrulamasını büyük ölçüde iyileştirir. Bu codelab'deki tüm çalışmalarınız tarayıcıda yapılabilir. Herhangi bir şey yüklemeniz gerekmez.
3. Ortamı hazırlama ve Cloud API'lerini etkinleştirme
Bu proje boyunca ihtiyaç duyacağımız çeşitli hizmetleri kullanabilmek için birkaç API etkinleştireceğiz. Bu işlemi Cloud Shell'de aşağıdaki komutu başlatarak gerçekleştireceğiz:
$ gcloud services enable \ appengine.googleapis.com \ cloudbuild.googleapis.com \ cloudfunctions.googleapis.com \ compute.googleapis.com \ firestore.googleapis.com \ run.googleapis.com
Bir süre sonra işlemin başarıyla tamamlandığını göreceksiniz:
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
Ayrıca bu süreçte ihtiyaç duyacağımız bir ortam değişkeni de oluşturacağız: işlevimizi, uygulamamızı ve container'ı dağıtacağımız bulut bölgesi.
$ export REGION=europe-west3
Verileri Cloud Firestore veritabanında depolayacağımız için bu veritabanını oluşturmamız gerekecek:
$ gcloud app create --region=${REGION} $ gcloud firestore databases create --location=${REGION}
Bu codelab'in ilerleyen bölümlerinde REST API'yi uygularken verileri sıralamamız ve filtrelememiz gerekecek. Bunun için üç dizin oluşturacağız:
$ gcloud firestore indexes composite create --collection-group=books \ --field-config field-path=language,order=ascending \ --field-config field-path=updated,order=descending $ gcloud firestore indexes composite create --collection-group=books \ --field-config field-path=author,order=ascending \ --field-config field-path=updated,order=descending
Bu 3 dizin, yazara veya dile göre yapacağımız aramalara karşılık gelir ve koleksiyondaki güncellenmiş alanla sıralamayı korur.
4. Kodu alın
Aşağıdaki GitHub deposundan kodu alın:
$ git clone https://github.com/glaforge/serverless-web-apis
Uygulama kodu, Node.JS kullanılarak yazılmıştır.
Bu laboratuvarla ilgili klasör yapınız aşağıdaki gibi olacaktır:
serverless-web-apis | ├── data | ├── books.json | ├── function-import | ├── index.js | ├── package.json | ├── run-crud | ├── index.js | ├── package.json | ├── Dockerfile | ├── appengine-frontend ├── public | ├── css/style.css | ├── html/index.html | ├── js/app.js ├── index.js ├── package.json ├── app.yaml
İlgili klasörler şunlardır:
data
— Bu klasör, 100 kitaptan oluşan bir listenin örnek verilerini içerir.function-import
: Bu işlev, örnek verileri içe aktarmak için bir uç nokta sunar.run-crud
: Bu kapsayıcı, Cloud Firestore'da depolanan kitap verilerine erişmek için bir Web API'sini açığa çıkarır.appengine-frontend
: Bu App Engine web uygulaması, kitap listesine göz atmak için basit bir salt okunur ön uç görüntüler.
5. Örnek kitap kitaplığı verileri
Veri klasöründe, muhtemelen okumaya değer yüz kitabından oluşan listeyi içeren bir books.json
dosyamız var. Bu JSON belgesi, JSON nesnelerini içeren bir dizidir. Cloud Functions işleviyle kullanacağımız verilerin şeklini inceleyelim:
[
{
"isbn": "9780435272463",
"author": "Chinua Achebe",
"language": "English",
"pages": 209,
"title": "Things Fall Apart",
"year": 1958
},
{
"isbn": "9781414251196",
"author": "Hans Christian Andersen",
"language": "Danish",
"pages": 784,
"title": "Fairy tales",
"year": 1836
},
...
]
Bu dizideki tüm kitap girişlerimiz aşağıdaki bilgileri içerir:
isbn
— Kitabı tanımlayan ISBN-13 kodu.author
: Kitabın yazarının adı.language
— Kitabın yazıldığı konuşma dili.pages
: Kitaptaki sayfa sayısı.title
: Kitabın başlığı.year
— Kitabın yayınlandığı yıl.
6. Örnek kitap verilerini içe aktarmak için kullanılan bir işlev uç noktası
Bu ilk bölümde, örnek kitap verilerini içe aktarmak için kullanılacak uç noktayı uygulayacağız. Bunun için Cloud Functions'ı kullanacağız.
Kodu inceleyin
package.json
dosyasını ele alarak başlayalım:
{
"name": "function-import",
"description": "Import sample book data",
"license": "Apache-2.0",
"dependencies": {
"@google-cloud/firestore": "^4.9.9"
},
"devDependencies": {
"@google-cloud/functions-framework": "^3.1.0"
},
"scripts": {
"start": "npx @google-cloud/functions-framework --target=parseBooks"
}
}
Çalışma zamanı bağımlılıklarında, veritabanına erişmek ve kitap verilerimizi depolamak için yalnızca @google-cloud/firestore
NPM modülüne ihtiyacımız vardır. Temel olarak Cloud Functions çalışma zamanı Express web çerçevesini de sağlar. Böylece, bunu bağımlılık olarak tanımlamamız gerekmez.
Geliştirme bağımlılıklarında, işlevlerinizi çağırmak için kullanılan çalışma zamanı çerçevesi olan Functions Framework'ü (@google-cloud/functions-framework
) tanımlıyoruz. Bu açık kaynak çerçeve, her değişiklik yaptığınızda dağıtım yapmadan işlevleri çalıştırmak ve böylece geliştirme geri bildirim döngüsünü iyileştirmek için makinenizde (bizim durumumuzda Cloud Shell'de) yerel olarak da kullanabileceğiniz bir açık kaynak çerçevedir.
Bağımlılıkları yüklemek için install
komutunu kullanın:
$ npm install
start
komut dosyası, size işlevi yerel olarak çalıştırmak üzere aşağıdaki talimatla birlikte kullanabileceğiniz bir komut vermek için Functions Çerçevesi'ni kullanır:
$ npm start
İşlevle etkileşime geçmek üzere HTTP GET istekleri için curl'ü veya Cloud Shell web önizlemesini kullanabilirsiniz.
Şimdi, kitap verilerini içe aktarma işlevimizin mantığını içeren index.js
dosyasına bakalım:
const Firestore = require('@google-cloud/firestore');
const firestore = new Firestore();
const bookStore = firestore.collection('books');
Firestore modülünü örneklendiririz ve kitap koleksiyonunu işaret ederiz (ilişkisel veritabanlarındaki tabloya benzer).
functions.http('parseBooks', async (req, resp) => {
if (req.method !== "POST") {
resp.status(405).send({error: "Only method POST allowed"});
return;
}
if (req.headers['content-type'] !== "application/json") {
resp.status(406).send({error: "Only application/json accepted"});
return;
}
...
})
parseBooks
JavaScript işlevini dışa aktarıyoruz. Bu, daha sonra dağıttığımızda açıklayacağımız işlevdir.
Sonraki birkaç talimatta şunlar kontrol edilir:
- Yalnızca HTTP
POST
isteklerini kabul ederiz. Aksi takdirde, diğer HTTP yöntemlerine izin verilmediğini belirtmek için405
durum kodu döndürürüz. - Yalnızca
application/json
yüklerini kabul ediyoruz ve bunun kabul edilebilir bir yük biçimi olmadığını belirtmek için406
durum kodu gönderiyoruz.
const books = req.body;
const writeBatch = firestore.batch();
for (const book of books) {
const doc = bookStore.doc(book.isbn);
writeBatch.set(doc, {
title: book.title,
author: book.author,
language: book.language,
pages: book.pages,
year: book.year,
updated: Firestore.Timestamp.now()
});
}
Ardından, isteğin body
öğesi aracılığıyla JSON yükünü alabiliriz. Tüm kitapları toplu olarak depolamak için bir Firestore toplu işlemi hazırlıyoruz. Kitap ayrıntılarını içeren JSON dizisi üzerinde isbn
, title
, author
, language
, pages
ve year
alanlarını kullanarak yineleme yaparız. Kitabın ISBN kodu, kitabın birincil anahtarı veya tanımlayıcısı olarak işlev görür.
try {
await writeBatch.commit();
console.log("Saved books in Firestore");
} catch (e) {
console.error("Error saving books:", e);
resp.status(400).send({error: "Error saving books"});
return;
};
resp.status(202).send({status: "OK"});
Artık toplu veriler hazır olduğuna göre işlemi gerçekleştirebiliriz. Depolama işlemi başarısız olursa işlemin başarısız olduğunu belirtmek için bir 400
durum kodu döndürürüz. Aksi takdirde, toplu kayıt isteğinin kabul edildiğini belirten 202
durum koduyla birlikte bir Tamam yanıtı döndürebiliriz.
İçe aktarma işlevini çalıştırma ve test etme
Kodu çalıştırmadan önce bağımlıları aşağıdaki öğelerle yükleyeceğiz:
$ npm install
İşlevi yerel olarak çalıştırmak için Functions Çerçevesi sayesinde package.json
içinde tanımladığımız start
komut dosyası komutunu kullanacağız:
$ npm start > start > npx @google-cloud/functions-framework --target=parseBooks Serving function... Function: parseBooks URL: http://localhost:8080/
Yerel işlevinize HTTP POST
isteği göndermek için şu komutu çalıştırabilirsiniz:
$ curl -d "@../data/books.json" \ -H "Content-Type: application/json" \ http://localhost:8080/
Bu komutu başlattığınızda, işlevin yerel olarak çalıştığını onaylayan aşağıdaki çıkışı görürsünüz:
{"status":"OK"}
Verilerin gerçekten Firestore'da depolanıp depolanmadığını kontrol etmek için Cloud Console kullanıcı arayüzüne de gidebilirsiniz:
Yukarıdaki ekran görüntüsünde, oluşturulan books
koleksiyonunu, kitap ISBN koduyla tanımlanan kitap belgelerinin listesini ve sağ tarafta söz konusu kitap girişinin ayrıntılarını görüyoruz.
İşlevi bulutta dağıtma
İşlevi Cloud Functions'da dağıtmak için function-import
dizininde aşağıdaki komutu kullanacağız:
$ gcloud functions deploy bulk-import \ --gen2 \ --trigger-http \ --runtime=nodejs20 \ --allow-unauthenticated \ --max-instances=30 --region=${REGION} \ --source=. \ --entry-point=parseBooks
İşlevi bulk-import
sembolik adıyla dağıtırız. Bu işlev HTTP istekleri aracılığıyla tetiklenir. Node.JS 20 çalışma zamanını kullanıyoruz. İşlevi herkese açık olarak dağıtırız (ideal olarak, bu uç noktanın güvenliğini sağlamalıyız). İşlevin bulunmasını istediğimiz bölgeyi belirtiriz. Ayrıca, yerel dizindeki kaynakları işaret ediyor ve giriş noktası olarak parseBooks
(dışa aktarılan JavaScript işlevi) kullanıyoruz.
Birkaç dakika veya daha kısa bir süre sonra işlev buluta dağıtılır. Cloud Console kullanıcı arayüzünde şu işlev gösterilir:
Dağıtım çıkışında, işlevinizin belirli bir adlandırma kuralına (https://${REGION}-${GOOGLE_CLOUD_PROJECT}.cloudfunctions.net/${FUNCTION_NAME}
) uyan URL'sini görebilirsiniz. Elbette bu HTTP tetikleyici URL'sini Cloud Console kullanıcı arayüzündeki tetikleyici sekmesinde de bulabilirsiniz:
URL'yi gcloud
kullanarak komut satırı üzerinden de alabilirsiniz:
$ export BULK_IMPORT_URL=$(gcloud functions describe bulk-import \ --region=$REGION \ --format 'value(httpsTrigger.url)') $ echo $BULK_IMPORT_URL
Bu değişkeni, dağıtılan işlevimizi test etmek amacıyla yeniden kullanmak için BULK_IMPORT_URL
ortam değişkeninde depolayalım.
Dağıtılan işlevi test etme
İşlevin yerel olarak çalıştığını test etmek için daha önce kullandığımız bir curl komutuyla dağıtılan işlevi test edeceğiz. Tek değişiklik şu URL olacaktır:
$ curl -d "@../data/books.json" \ -H "Content-Type: application/json" \ $BULK_IMPORT_URL
Yine başarılı olursa aşağıdaki çıkışı döndürür:
{"status":"OK"}
İçe aktarma işlevimiz dağıtıldığına ve örnek verilerimizi yüklediğimize göre artık bu veri kümesini açığa çıkaran REST API'yi geliştirme zamanı geldi.
7. REST API sözleşmesi
Örneğin, Open API spesifikasyonu kullanarak bir API sözleşmesi tanımlamasak da REST API'mizin çeşitli uç noktalarına göz atacağız.
API exchange'leri aşağıdakilerden oluşan JSON nesnelerini rezerve eder:
isbn
(isteğe bağlı): Geçerli bir ISBN kodunu temsil eden 13 karakterlikString
author
— kitabın yazarının adını temsil eden boş olmayan birString
,language
— kitabın yazıldığı dili içeren boş olmayan birString
,pages
— kitabın sayfa sayısı için pozitifInteger
,title
— kitabın başlığını içeren boş birString
,year
— Kitabın yayın yılı içinInteger
değeri.
Örnek kitap yükü:
{
"isbn": "9780435272463",
"author": "Chinua Achebe",
"language": "English",
"pages": 209,
"title": "Things Fall Apart",
"year": 1958
}
/books'u edinin
Yazara ve/veya dile göre filtrelenmiş ve tek seferde 10 sonuç pencerelerine göre sayfalara ayrılmış tüm kitapların listesini alın.
Vücut yükü: Yok.
Sorgu parametreleri:
author
(isteğe bağlı) — kitap listesini yazara göre filtreler,language
(isteğe bağlı) — kitap listesini dile göre filtreler,page
(isteğe bağlı, varsayılan = 0) — Döndürülecek sonuç sayfasının sıralamasını gösterir.
İadeler: Kitap nesnelerinden oluşan bir JSON dizisi.
Durum kodları:
200
— istek, kitap listesini getirmeyi başardığında,400
- Bir hata oluşursa.
/kitaplar ve POST /books/{isbn} YAYINLA
isbn
yol parametresiyle (bu durumda, kitap yükünde isbn
koduna gerek yoktur) veya içermeyen (bu durumda, kitap yükünde isbn
kodu bulunmalıdır) yeni bir kitap yükü yayınlayın
Gövde yükü: Bir kitap nesnesi.
Sorgu parametreleri: yok.
Şunu döndürür: Hiçbir şey.
Durum kodları:
201
— kitap başarıyla depolandığında,406
:isbn
kodu geçersizse400
- Bir hata oluşursa.
/books/{isbn} AL
Kitaplıktan isbn
koduyla tanımlanan ve yol parametresi olarak iletilen bir kitabı alır.
Vücut yükü: Yok.
Sorgu parametreleri: yok.
Bir kitabın JSON nesnesi veya kitap yoksa bir hata nesnesi döndürür.
Durum kodları:
200
— Kitap, veritabanında bulunursa400
— Bir hata oluşursa404
— Kitap bulunamazsa406
—isbn
kodu geçersizse.
PUT /books/{isbn}
Yol parametresi olarak iletilen isbn
ile tanımlanan mevcut bir kitabı günceller.
Gövde yükü: Bir kitap nesnesi. Yalnızca güncellenmesi gereken alanlar aktarılabilir. Diğer alanlar isteğe bağlıdır.
Sorgu parametreleri: yok.
Şunu döndürür: güncellenen kitap.
Durum kodları:
200
— kitap başarıyla güncellendiğinde,400
— Bir hata oluşursa406
—isbn
kodu geçersizse.
/books/{isbn} SİLİN
Yol parametresi olarak iletilen isbn
ile tanımlanmış mevcut bir kitabı siler.
Vücut yükü: Yok.
Sorgu parametreleri: yok.
Şunu döndürür: Hiçbir şey.
Durum kodları:
204
— kitap başarıyla silindiğinde,400
- Bir hata oluşursa.
8. Container'da REST API dağıtma ve kullanıma sunma
Kodu inceleyin
Dockerfile
Öncelikle, uygulama kodumuzu container mimarisine almaktan sorumlu olacak Dockerfile
öğesine bakalım:
FROM node:20-slim
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --only=production
COPY . ./
CMD [ "node", "index.js" ]
Bir Node.JS 20 "slim" görüntüsü kullanıyoruz. /usr/src/app
dizininde çalışıyoruz. Diğer unsurların yanı sıra bağımlılıklarımızı tanımlayan package.json
dosyasını (ayrıntılar aşağıda verilmiştir) kopyalıyoruz. Bağımlılıkları, kaynak kodunu kopyalayarak npm install
ile yükleriz. Son olarak, node index.js
komutuyla bu uygulamanın nasıl çalıştırılacağını belirtiriz.
package.json
Sonra, package.json
dosyasına bakabiliriz:
{
"name": "run-crud",
"description": "CRUD operations over book data",
"license": "Apache-2.0",
"engines": {
"node": ">= 20.0.0"
},
"dependencies": {
"@google-cloud/firestore": "^4.9.9",
"cors": "^2.8.5",
"express": "^4.17.1",
"isbn3": "^1.1.10"
},
"scripts": {
"start": "node index.js"
}
}
Dockerfile
ürününde olduğu gibi, Node.JS 14'ü kullanmak istediğimizi belirtiriz.
Web API uygulamamız aşağıdakilere bağlıdır:
- Veritabanındaki kitap verilerine erişmek için Firestore NPM modülü
- REST API'miz App Engine web uygulaması ön ucumuzun istemci kodundan çağrılacağı için CORS (Kaynaklar Arası Kaynak Paylaşımı) isteklerini işleyecek
cors
kitaplığı, - API'mizi tasarlamak için web çerçevemiz olacak olan Express çerçevesi,
- Ardından, kitap ISBN kodlarını doğrulamaya yardımcı olan
isbn3
modülü.
Ayrıca, geliştirme ve test amacıyla, uygulamayı yerel olarak başlatmak için kullanışlı olacak start
komut dosyasını da belirtiriz.
index.js
Şimdi, index.js
konusunu yakından inceleyerek kodun temeline geçelim:
const Firestore = require('@google-cloud/firestore');
const firestore = new Firestore();
const bookStore = firestore.collection('books');
Firestore modülüne ihtiyacımız var ve kitap verilerimizin saklandığı books
koleksiyonuna referans veriyoruz.
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json());
const querystring = require('querystring');
const cors = require('cors');
app.use(cors({
exposedHeaders: ['Content-Length', 'Content-Type', 'Link'],
}));
REST API'mizi uygulamak için web çerçevemiz olarak Express'i kullanıyoruz. API'mizle değiştirilen JSON yüklerini ayrıştırmak için body-parser
modülünü kullanıyoruz.
querystring
modülü, URL'leri değiştirme konusunda faydalıdır. Bu durum, sayfalara ayırma amacıyla Link
başlıkları oluşturduğumuzda mümkün olacaktır (ileride bu konuda daha fazla bilgi verilecektir).
Ardından cors
modülünü yapılandırırız. CORS aracılığıyla aktarılmasını istediğimiz başlıkları açıkça belirtiriz, çünkü çoğu genellikle kaldırılmış olur, ancak burada normal içerik uzunluğu ve türünün yanı sıra sayfalara ayırma için belirteceğimiz Link
başlığını korumak istiyoruz.
const ISBN = require('isbn3');
function isbnOK(isbn, res) {
const parsedIsbn = ISBN.parse(isbn);
if (!parsedIsbn) {
res.status(406)
.send({error: `Invalid ISBN: ${isbn}`});
return false;
}
return parsedIsbn;
}
ISBN kodlarını ayrıştırıp doğrulamak için isbn3
NPM modülünü kullanacağız. Ayrıca, ISBN kodları geçersizse ISBN kodlarını ayrıştıracak ve yanıta 406
durum koduyla yanıt verecek küçük bir yardımcı program işlevi geliştireceğiz.
GET /books
GET /books
uç noktasına tek tek göz atalım:
app.get('/books', async (req, res) => {
try {
var query = new Firestore().collection('books');
if (!!req.query.author) {
console.log(`Filtering by author: ${req.query.author}`);
query = query.where("author", "==", req.query.author);
}
if (!!req.query.language) {
console.log(`Filtering by language: ${req.query.language}`);
query = query.where("language", "==", req.query.language);
}
const page = parseInt(req.query.page) || 0;
// - - ✄ - - ✄ - - ✄ - - ✄ - - ✄ - -
} catch (e) {
console.error('Failed to fetch books', e);
res.status(400)
.send({error: `Impossible to fetch books: ${e.message}`});
}
});
Bir sorgu hazırlayarak veritabanını sorgulamaya hazırlanıyoruz. Bu sorgu, yazara ve/veya dile göre filtrelemek için isteğe bağlı sorgu parametrelerine bağlı olacak. Ayrıca, 10 kitaplık parçalar halinde kitap listesini iade ediyoruz.
Bu işlem sırasında bir hata oluşursa kitaplar getirilirken 400 durum kodu içeren bir hata döndürülür.
Bu uç noktanın kırpılmış bölümünü yakından inceleyelim:
const snapshot = await query
.orderBy('updated', 'desc')
.limit(PAGE_SIZE)
.offset(PAGE_SIZE * page)
.get();
const books = [];
if (snapshot.empty) {
console.log('No book found');
} else {
snapshot.forEach(doc => {
const {title, author, pages, year, language, ...otherFields} = doc.data();
const book = {isbn: doc.id, title, author, pages, year, language};
books.push(book);
});
}
Önceki bölümde author
ve language
ölçütlerine göre filtreleme yaptık ancak bu bölümde, kitap listesini son güncelleme tarihine göre sıralayacağız (son güncelleme önce gelir). Ayrıca, bir sınır (döndürülecek öğe sayısı) ve ofset (bir sonraki kitap grubunun döndürüleceği başlangıç noktası) tanımlayarak sonucu sayfalara ayıracağız.
Sorguyu yürütür, verilerin anlık görüntüsünü alırız ve bu sonuçları, işlevin sonunda döndürülecek bir JavaScript dizisine yerleştiririz.
İyi bir uygulamaya göz atarak bu uç noktanın açıklamalarını tamamlayalım: Verinin ilk, önceki, sonraki veya son sayfalarına giden URI bağlantılarını tanımlamak için Link
başlığını kullanmak (bu durumda yalnızca önceki ve sonrakini sağlayacağız).
var links = {};
if (page > 0) {
const prevQuery = querystring.stringify({...req.query, page: page - 1});
links.prev = `${req.path}${prevQuery != '' ? `?${prevQuery}` : ''}`;
}
if (snapshot.docs.length === PAGE_SIZE) {
const nextQuery = querystring.stringify({...req.query, page: page + 1});
links.next = `${req.path}${nextQuery != '' ? `?${nextQuery}` : ''}`;
}
if (Object.keys(links).length > 0) {
res.links(links);
}
res.status(200).send(books);
Buradaki mantık ilk başta biraz karmaşık görünebilir, ancak verilerin ilk sayfasında değilsek önceki bağlantıyı eklemeyi planlıyoruz. Veri sayfası doluysa (yani PAGE_SIZE
sabit değeri ile tanımlanan maksimum kitap sayısını içeriyorsa, daha fazla verinin ortaya çıktığını varsayarsak) bir next bağlantısı ekleriz. Ardından, doğru söz dizimiyle doğru başlığı oluşturmak için Express'in resource#links()
işlevini kullanırız.
Bağlantı üstbilgisinin görünümü aşağıdaki gibi olacaktır:
link: </books?page=1>; rel="prev", </books?page=3>; rel="next"
POST /books
vePOST /books/:isbn
Her iki uç nokta da yeni bir kitap oluşturmak için kullanılır. Biri kitap yükünde ISBN kodunu, diğeri ise yol parametresi olarak iletir. Her iki durumda da createBook()
fonksiyonumuzu çağırın:
async function createBook(isbn, req, res) {
const parsedIsbn = isbnOK(isbn, res);
if (!parsedIsbn) return;
const {title, author, pages, year, language} = req.body;
try {
const docRef = bookStore.doc(parsedIsbn.isbn13);
await docRef.set({
title, author, pages, year, language,
updated: Firestore.Timestamp.now()
});
console.log(`Saved book ${parsedIsbn.isbn13}`);
res.status(201)
.location(`/books/${parsedIsbn.isbn13}`)
.send({status: `Book ${parsedIsbn.isbn13} created`});
} catch (e) {
console.error(`Failed to save book ${parsedIsbn.isbn13}`, e);
res.status(400)
.send({error: `Impossible to create book ${parsedIsbn.isbn13}: ${e.message}`});
}
}
isbn
kodunun geçerli olup olmadığını kontrol ederiz. Aksi takdirde, işlevden döneriz (ve 406
durum kodu ayarlarız). Kitap alanlarını, isteğin gövdesinde iletilen yükten alırız. Ardından kitap ayrıntılarını Firestore'da saklayacağız. Başarılı olarak 201
, başarısızlık durumunda ise 400
geri dönüyor.
Başarıyla geri döndüğünde, yeni oluşturulan kaynağın bulunduğu API istemcisine ipuçları vermek için konum başlığını da ayarlarız. Başlık aşağıdaki gibi görünür:
Location: /books/9781234567898
GET /books/:isbn
ISBN'si ile tanımlanan bir kitabı Firestore'dan getirelim.
app.get('/books/:isbn', async (req, res) => {
const parsedIsbn = isbnOK(req.params.isbn, res);
if (!parsedIsbn) return;
try {
const docRef = bookStore.doc(parsedIsbn.isbn13);
const docSnapshot = await docRef.get();
if (!docSnapshot.exists) {
console.log(`Book not found ${parsedIsbn.isbn13}`)
res.status(404)
.send({error: `Could not find book ${parsedIsbn.isbn13}`});
return;
}
console.log(`Fetched book ${parsedIsbn.isbn13}`, docSnapshot.data());
const {title, author, pages, year, language, ...otherFields} = docSnapshot.data();
const book = {isbn: parsedIsbn.isbn13, title, author, pages, year, language};
res.status(200).send(book);
} catch (e) {
console.error(`Failed to fetch book ${parsedIsbn.isbn13}`, e);
res.status(400)
.send({error: `Impossible to fetch book ${parsedIsbn.isbn13}: ${e.message}`});
}
});
Her zaman olduğu gibi ISBN'nin geçerli olup olmadığını kontrol ederiz. Kitabı almak için Firestore'a bir sorgu göndeririz. snapshot.exists
özelliği, gerçekten bir kitabın bulunup bulunmadığını anlamak açısından kullanışlıdır. Aksi takdirde, hata mesajı ve 404
Bulunamadı durum kodu gönderilir. Kitap verilerini alırız ve döndürülecek kitabı temsil eden bir JSON nesnesi oluştururuz.
PUT /books/:isbn
Mevcut bir kitabı güncellemek için PUT yöntemini kullanıyoruz.
app.put('/books/:isbn', async (req, res) => {
const parsedIsbn = isbnOK(req.params.isbn, res);
if (!parsedIsbn) return;
try {
const docRef = bookStore.doc(parsedIsbn.isbn13);
await docRef.set({
...req.body,
updated: Firestore.Timestamp.now()
}, {merge: true});
console.log(`Updated book ${parsedIsbn.isbn13}`);
res.status(201)
.location(`/books/${parsedIsbn.isbn13}`)
.send({status: `Book ${parsedIsbn.isbn13} updated`});
} catch (e) {
console.error(`Failed to update book ${parsedIsbn.isbn13}`, e);
res.status(400)
.send({error: `Impossible to update book ${parsedIsbn.isbn13}: ${e.message}`});
}
});
updated
tarih/saat alanını, bu kaydı en son ne zaman güncellediğimizi hatırlamak için güncelleriz. Mevcut alanları yeni değerlerle değiştiren {merge:true}
stratejisini kullanırız (aksi takdirde tüm alanlar kaldırılır ve yalnızca yükteki yeni alanlar kaydedilerek önceki güncellemedeki veya ilk oluşturmadaki mevcut alanlar silinir).
Ayrıca Location
başlığını, kitabın URI'sını işaret edecek şekilde ayarladık.
DELETE /books/:isbn
Kitapları silmek oldukça kolaydır. Belge referansında yalnızca delete()
yöntemini çağırıyoruz. Herhangi bir içerik döndürmediğimiz için bir 204 durum kodu döndürürüz.
app.delete('/books/:isbn', async (req, res) => {
const parsedIsbn = isbnOK(req.params.isbn, res);
if (!parsedIsbn) return;
try {
const docRef = bookStore.doc(parsedIsbn.isbn13);
await docRef.delete();
console.log(`Book ${parsedIsbn.isbn13} was deleted`);
res.status(204).end();
} catch (e) {
console.error(`Failed to delete book ${parsedIsbn.isbn13}`, e);
res.status(400)
.send({error: `Impossible to delete book ${parsedIsbn.isbn13}: ${e.message}`});
}
});
Ekspres / Düğüm sunucusunu başlatma
Son olarak, varsayılan olarak 8080
bağlantı noktasını dinleyerek sunucuyu başlatırız:
const port = process.env.PORT || 8080;
app.listen(port, () => {
console.log(`Books Web API service: listening on port ${port}`);
console.log(`Node ${process.version}`);
});
Uygulamayı yerel olarak çalıştırma
Uygulamayı yerel olarak çalıştırmak için öncelikle bağımlılıkları şu uygulamalarla yükleyeceğiz:
$ npm install
Şimdi şöyle başlayabiliriz:
$ npm start
Sunucu varsayılan olarak localhost
itibarıyla başlatılır ve 8080 numaralı bağlantı noktasında dinleme yapar.
Aşağıdaki komutları kullanarak Docker container'ı derleyebilir ve container görüntüsünü çalıştırabilirsiniz:
$ docker build -t crud-web-api . $ docker run --rm -p 8080:8080 -it crud-web-api
Docker'da çalışmak, uygulamamızı Cloud Build ile bulutta derlerken container mimarisine alma işleminin düzgün çalışıp çalışmadığını tekrar kontrol etmenin mükemmel bir yoludur.
API'yi test etme
REST API kodunu doğrudan Düğüm veya Docker container görüntüsü üzerinden nasıl çalıştırdığımıza bakılmaksızın birkaç sorgu çalıştırabiliyoruz.
- Yeni bir kitap oluşturun (gövde yükünde ISBN):
$ curl -XPOST -d '{"isbn":"9782070368228","title":"Book","author":"me","pages":123,"year":2021,"language":"French"}' \ -H "Content-Type: application/json" \ http://localhost:8080/books
- Yeni bir kitap oluşturun (yol parametresinde ISBN):
$ curl -XPOST -d '{"title":"Book","author":"me","pages":123,"year":2021,"language":"French"}' \ -H "Content-Type: application/json" \ http://localhost:8080/books/9782070368228
- Bir kitabı (oluşturduğumuz) silme:
$ curl -XDELETE http://localhost:8080/books/9782070368228
- ISBN'ye göre kitap alma:
$ curl http://localhost:8080/books/9780140449136 $ curl http://localhost:8080/books/9782070360536
- Mevcut bir kitabı yalnızca başlığını değiştirerek güncelleme:
$ curl -XPUT \ -d '{"title":"Book"}' \ -H "Content-Type: application/json" \ http://localhost:8080/books/9780003701203
- Kitap listesini (ilk 10) al:
$ curl http://localhost:8080/books
- Belirli bir yazar tarafından yazılmış kitapları bulmak için:
$ curl http://localhost:8080/books?author=Virginia+Woolf
- İngilizce yazılmış kitapları listeleyin:
$ curl http://localhost:8080/books?language=English
- Kitapların 4. sayfasını yükle:
$ curl http://localhost:8080/books?page=3
Ayrıca, aramamızı daraltmak için author
, language
ve books
sorgu parametrelerini birleştirebiliriz.
Container mimarisine alınmış REST API'yi oluşturma ve dağıtma
REST API'nin plana göre çalıştığından mutluluk duyuyoruz. Bu, REST API'yi Cloud Run'da Cloud'da dağıtmak için doğru zamandır.
Bunu iki adımda gerçekleştireceğiz:
- İlk olarak aşağıdaki komutla Cloud Build ile container görüntüsünü oluşturun:
$ gcloud builds submit \ --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/crud-web-api
- Ardından, hizmeti şu ikinci komutla dağıtarak:
$ gcloud run deploy run-crud \ --image gcr.io/${GOOGLE_CLOUD_PROJECT}/crud-web-api \ --allow-unauthenticated \ --region=${REGION} \ --platform=managed
Cloud Build, ilk komutla container görüntüsünü oluşturur ve Container Registry'de barındırır. Sıradaki komut, kayıt defterinden container görüntüsünü dağıtır ve bulut bölgesinde dağıtır.
Cloud Console kullanıcı arayüzünde, Cloud Run hizmetimizin artık listede yer alıp almadığını tekrar kontrol edebiliriz:
Burada da uygulayacağımız son adım, aşağıdaki komut sayesinde yeni dağıtılan Cloud Run hizmetinin URL'sini almaktır:
$ export RUN_CRUD_SERVICE_URL=$(gcloud run services describe run-crud \ --region=${REGION} \ --platform=managed \ --format='value(status.url)')
App Engine ön uç kodumuz API ile etkileşime gireceğinden sonraki bölümde Cloud Run REST API'mizin URL'sine ihtiyacımız olacak.
9. Kitaplığa göz atmak için bir web uygulaması barındırın
Yapbozun bu projeyi iyileştirecek son parçası, REST API'mizle etkileşim kuracak bir web ön ucu sağlamak. Bu amaçla Google App Engine'i, AJAX istekleri aracılığıyla (istemci tarafı Fetch API'sini kullanarak) API'yi çağıracak bazı istemci JavaScript kodlarıyla kullanacağız.
Uygulamamız, Node.JS App Engine çalışma zamanında dağıtılmış olsa da çoğunlukla statik kaynaklardan oluşuyor. Kullanıcı etkileşiminin büyük kısmı istemci taraflı JavaScript üzerinden tarayıcıda gerçekleşeceğinden fazla arka uç kodu yoktur. Gösterişli ön uç JavaScript çerçevesi kullanmayacağız. Yalnızca Shoelace web bileşeni kitaplığını kullanarak kullanıcı arayüzü için birkaç Web Bileşeni ile birlikte "vanilla" JavaScript kullanacağız:
- kitabın dilini seçmek için bir seçim kutusu:
- Belirli bir kitapla ilgili ayrıntıları görüntülemek için bir kart bileşeni (JsBarcode kitaplığını kullanarak kitabın ISBN'sini temsil eden bir barkod dahil):
- ve veritabanından daha fazla kitap yüklemek için bir düğme:
Tüm bu görsel bileşenleri bir araya getirdiğimizde, kitaplığımıza göz atabileceğiniz web sayfası aşağıdaki gibi görünecektir:
app.yaml
yapılandırma dosyası
app.yaml
yapılandırma dosyasına bakarak bu App Engine uygulamasının kod tabanını incelemeye başlayalım. Bu, App Engine'e özel bir dosyadır ve ortam değişkenleri, uygulamanın çeşitli "işleyicileri" gibi şeyleri yapılandırmanızı veya bazı kaynakların App Engine'in yerleşik CDN'si tarafından sunulacak statik öğeler olduğunu belirtmenizi sağlar.
runtime: nodejs14
env_variables:
RUN_CRUD_SERVICE_URL: CHANGE_ME
handlers:
- url: /js
static_dir: public/js
- url: /css
static_dir: public/css
- url: /img
static_dir: public/img
- url: /(.+\.html)
static_files: public/html/\1
upload: public/(.+\.html)
- url: /
static_files: public/html/index.html
upload: public/html/index\.html
- url: /.*
secure: always
script: auto
Uygulamamızın Node.JS olduğunu ve sürüm 14'ü kullanmak istediğimizi belirtiyoruz.
Ardından, Cloud Run hizmet URL'sini işaret eden bir ortam değişkeni tanımlıyoruz. CHANGE_ME yer tutucusunu doğru URL ile güncellememiz gerekir (bunu nasıl değiştireceğinizi aşağıdan öğrenebilirsiniz).
Bundan sonra, çeşitli işleyiciler tanımlarız. İlk 3'ü, public/
klasörü ve alt klasörleri altındaki HTML, CSS ve JavaScript istemci tarafı kod konumunu gösteriyor. Dördüncüsü, App Engine uygulamamızın kök URL'sinin index.html
sayfasına yönlendirmesi gerektiğini belirtir. Bu şekilde, web sitesinin kök dizinine erişirken URL'de index.html
son ekini görmeyiz. Sonuncusu ise varsayılan olarak diğer tüm URL'leri (/.*
) Node.JS uygulamamıza (açıkladığımız statik öğelerin aksine, uygulamanın "dinamik" bölümü) yönlendirir.
Şimdi Cloud Run hizmetinin Web API URL'sini güncelleyelim.
appengine-frontend/
dizininde, Cloud Run tabanlı REST API'mizin URL'sini gösteren ortam değişkenini güncellemek için aşağıdaki komutu çalıştırın:
$ sed -i -e "s|CHANGE_ME|${RUN_CRUD_SERVICE_URL}|" app.yaml
Alternatif olarak, app.yaml
içindeki CHANGE_ME
dizesini doğru URL ile manuel olarak değiştirin:
env_variables:
RUN_CRUD_SERVICE_URL: CHANGE_ME
Node.JS package.json
dosyası
{
"name": "appengine-frontend",
"description": "Web frontend",
"license": "Apache-2.0",
"main": "index.js",
"engines": {
"node": "^14.0.0"
},
"dependencies": {
"express": "^4.17.1",
"isbn3": "^1.1.10"
},
"devDependencies": {
"nodemon": "^2.0.7"
},
"scripts": {
"start": "node index.js",
"dev": "nodemon --watch server --inspect index.js"
}
}
Bu uygulamayı Node.JS 14 kullanarak çalıştırmak istediğimizi tekrar vurgulamak isteriz. Kitapların doğrulanması için Express çerçevesinin yanı sıra isbn3
NPM modülüne güveniyoruz ISBN kodları.
Geliştirme bağımlılıklarında, dosya değişikliklerini izlemek için nodemon
modülünü kullanacağız. Uygulamamızı npm start
ile yerel olarak çalıştırabilsek de kodda bazı değişiklikler yapabilir, uygulamayı ^C
ile durdurup yeniden başlatabiliriz. Ancak bu işlem biraz can sıkıcı. Bunun yerine, değişiklikler yapıldıktan sonra uygulamanın otomatik olarak yeniden yüklenmesini / yeniden başlatılmasını sağlamak için aşağıdaki komutu kullanabiliriz:
$ npm run dev
index.js
Node.JS kodu
const express = require('express');
const app = express();
app.use(express.static('public'));
const bodyParser = require('body-parser');
app.use(bodyParser.json());
Express web çerçevesi gereklidir. Genel dizinin, static
ara yazılımı tarafından sunulabilecek (en azından geliştirme modunda yerel olarak çalışırken) statik öğeler içerdiğini belirtiriz. Son olarak, JSON yüklerimizin ayrıştırılması için body-parser
gerekir.
Tanımladığımız iki rotaya göz atalım:
app.get('/', async (req, res) => {
res.redirect('/html/index.html');
});
app.get('/webapi', async (req, res) => {
res.send(process.env.RUN_CRUD_SERVICE_URL);
});
/
ile eşleşen ilk öğe, public/html
dizinimizdeki index.html
öğesine yönlendirir. Geliştirme modunda App Engine çalışma zamanı içinde çalışmadığımızdan, App Engine'in URL yönlendirmesini devreye sokmuyoruz. Bunun yerine burada kök URL'yi HTML dosyasına yönlendiriyoruz.
/webapi
tanımladığımız ikinci uç nokta, Cloud RUN REST API'mizin URL'sini döndürür. Bu şekilde, istemci taraflı JavaScript kodu, kitap listesini almak için nereye çağrılacağını bilir.
const port = process.env.PORT || 8080;
app.listen(port, () => {
console.log(`Book library web frontend: listening on port ${port}`);
console.log(`Node ${process.version}`);
console.log(`Web API endpoint ${process.env.RUN_CRUD_SERVICE_URL}`);
});
İşlemi tamamlamak için Express web uygulamasını çalıştırıyoruz ve varsayılan olarak 8080 numaralı bağlantı noktasından dinliyoruz.
index.html
sayfası
Bu uzun HTML sayfasının her satırına bakmayacağız. Bunun yerine bazı önemli satırları vurgulayalım.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.37/dist/themes/base.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.37/dist/shoelace.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.0/dist/barcodes/JsBarcode.ean-upc.min.js"></script>
<script src="/js/app.js"></script>
<link rel="stylesheet" type="text/css" href="/css/style.css">
İlk iki satır, Ayakkabı Bağcığı web bileşeni kitaplığını (bir komut dosyası ve bir stil sayfası) içe aktarır.
Sonraki satır, kitap ISBN kodlarının barkodlarını oluşturmak için JsBarcode kitaplığını içe aktarır.
Son satırlar, public/
alt dizinlerimizde bulunan kendi JavaScript kodumuzu ve CSS stil sayfamızı içe aktarıyor.
HTML sayfasının body
bölümünde Ayakkabı Bağcığı bileşenlerini özel öğe etiketleriyle kullanırız. Örneğin:
<sl-icon name="book-half"></sl-icon>
...
<sl-select id="language-select" placeholder="Select a language..." clearable>
<sl-menu-item value="English">English</sl-menu-item>
<sl-menu-item value="French">French</sl-menu-item>
...
</sl-select>
...
<sl-button id="more-button" type="primary" size="large">
More books...
</sl-button>
...
Ayrıca, bir kitabı temsil etmek için HTML şablonlarını ve bunların yuvayı doldurma özelliğini de kullanıyoruz. Kitap listesini doldurmak için bu şablonun kopyalarını oluşturur ve alanlardaki değerleri kitapların ayrıntılarıyla değiştiririz:
<template id="book-card">
<sl-card class="card-overview">
...
<slot name="author">Author</slot>
...
</sl-card>
</template>
Bu kadar HTML yeterliyse kodu incelememiz bitmek üzere. Kalan son bir önemli bölüm: REST API'mizle etkileşime giren istemci taraflı app.js
JavaScript kodu.
app.js istemci tarafı JavaScript kodu
DOM içeriğinin yüklenmesini bekleyen üst düzey bir etkinlik işleyici ile başlıyoruz:
document.addEventListener("DOMContentLoaded", async function(event) {
...
}
Hazır olduğunda bazı önemli sabit değerler ve değişkenler oluşturabiliriz:
const serverUrlResponse = await fetch('/webapi');
const serverUrl = await serverUrlResponse.text();
console.log('Web API endpoint:', serverUrl);
const server = serverUrl + '/books';
var page = 0;
var language = '';
Öncelikle, başlangıçta app.yaml
içinde ayarladığımız ortam değişkenini döndüren App Engine düğüm kodumuz sayesinde REST API'mizin URL'sini getireceğiz. JavaScript istemci tarafı kodundan çağrılan /webapi
uç noktası sayesinde ortam değişkeni sayesinde REST API URL'sini ön uç kodumuza gömmemiz gerekmedi.
Ayrıca, sayfalara ayırma ve dil filtrelemeyi izlemek için kullanacağımız page
ve language
değişkenlerini de tanımlıyoruz.
const moreButton = document.getElementById('more-button');
moreButton.addEventListener('sl-focus', event => {
console.log('Button clicked');
moreButton.blur();
appendMoreBooks(server, page++, language);
});
Kitapları yüklemek için düğmeye bir etkinlik işleyici ekledik. Tıklandığında appendMoreBooks()
işlevi çağrılır.
const langSelect = document.getElementById('language-select');
langSelect.addEventListener('sl-change', event => {
page = 0;
language = event.srcElement.value;
document.getElementById('library').replaceChildren();
console.log(`Language selected: "${language}"`);
appendMoreBooks(server, page++, language);
});
Seçim kutusuna benzer şekilde, dil seçimindeki değişikliklerin bildirilmesi için bir etkinlik işleyici ekleriz. Düğmede olduğu gibi appendMoreBooks()
işlevini de çağırarak REST API URL'sini, geçerli sayfayı ve dil seçimini iletiriz.
Şimdi kitapları getiren ve ekleyen fonksiyona bir göz atalım:
async function appendMoreBooks(server, page, language) {
const searchUrl = new URL(server);
if (!!page) searchUrl.searchParams.append('page', page);
if (!!language) searchUrl.searchParams.append('language', language);
const response = await fetch(searchUrl.href);
const books = await response.json();
...
}
Yukarıda, REST API'yi çağırmak için kullanılacak tam URL'yi oluşturuyoruz. Normalde belirtebileceğimiz üç sorgu parametresi vardır, ancak burada bu kullanıcı arayüzünde yalnızca iki parametre belirtilmektedir:
page
- Kitapların sayfalara ayrılması için geçerli sayfayı gösteren bir tam sayılanguage
: Yazı diline göre filtrelenen bir dil dizesi.
Daha sonra, kitap ayrıntılarını içeren JSON dizisini almak için Getirme API'sini kullanırız.
const linkHeader = response.headers.get('Link')
console.log('Link', linkHeader);
if (!!linkHeader && linkHeader.indexOf('rel="next"') > -1) {
console.log('Show more button');
document.getElementById('buttons').style.display = 'block';
} else {
console.log('Hide more button');
document.getElementById('buttons').style.display = 'none';
}
Yanıtta Link
başlığının mevcut olup olmadığına bağlı olarak, [More books...]
düğmesini gösteririz veya gizleriz. Link
başlığı, yüklenmeye devam edecek daha fazla kitap olup olmadığını bize bildiren bir ipucudur (Link
başlığında next
URL'si bulunur).
const library = document.getElementById('library');
const template = document.getElementById('book-card');
for (let book of books) {
const bookCard = template.content.cloneNode(true);
bookCard.querySelector('slot[name=title]').innerText = book.title;
bookCard.querySelector('slot[name=language]').innerText = book.language;
bookCard.querySelector('slot[name=author]').innerText = book.author;
bookCard.querySelector('slot[name=year]').innerText = book.year;
bookCard.querySelector('slot[name=pages]').innerText = book.pages;
const img = document.createElement('img');
img.setAttribute('id', book.isbn);
img.setAttribute('class', 'img-barcode-' + book.isbn)
bookCard.querySelector('slot[name=barcode]').appendChild(img);
library.appendChild(bookCard);
...
}
}
İşlevin yukarıdaki bölümünde, REST API'si tarafından döndürülen her kitap için şablonu, bir kitabı temsil eden bazı web bileşenleriyle klonlayacak, şablondaki alanları kitabın ayrıntılarıyla dolduracağız.
JsBarcode('.img-barcode-' + book.isbn).EAN13(book.isbn, {fontSize: 18, textMargin: 0, height: 60}).render();
ISBN kodunu daha hoş hale getirmek için, gerçek kitapların arka kapağına benzer güzel bir barkod oluşturmak üzere JsBarcode kitaplığını kullanıyoruz.
Uygulamayı yerel olarak çalıştırma ve test etme
Şimdilik bu kadar kod yeterli. Şimdi sıra, uygulamanın nasıl çalıştığını görmeye geldi. İlk olarak, gerçek bir dağıtım yapmadan önce yerel olarak Cloud Shell'de bu işlemi gerçekleştireceğiz.
Uygulamamızın ihtiyaç duyduğu NPM modüllerini şunlarla yüklüyoruz:
$ npm install
Uygulamayı her zamanki gibi çalıştırırız:
$ npm start
Dilerseniz nodemon
sayesinde değişikliklerin otomatik olarak yeniden yüklenmesini sağlayabilirsiniz. Bu özellik şunları içerir:
$ npm run dev
Uygulama yerel olarak çalışıyor ve http://localhost:8080
adresine giderek uygulamaya tarayıcıdan erişebiliyoruz.
App Engine uygulamasını dağıtma
Uygulamamızın yerel olarak düzgün çalıştığından emin olduğumuza göre şimdi App Engine'de dağıtmanın zamanı geldi.
Uygulamayı dağıtmak için şu komutu çalıştıralım:
$ gcloud app deploy -q
Yaklaşık bir dakika sonra uygulamanın dağıtılması gerekir.
Uygulama, https://${GOOGLE_CLOUD_PROJECT}.appspot.com
şeklinin URL'sinde kullanılabilir olacaktır.
App Engine web uygulamamızın kullanıcı arayüzünü keşfetme
Artık şunları yapabilirsiniz:
- Daha fazla kitap yüklemek için
[More books...]
düğmesini tıklayın. - Belirli bir dili seçerek yalnızca o dildeki kitapları görebilirsiniz.
- Tüm kitapların listesine dönmek için seçimi, seçim kutusundaki küçük çarpı ile temizleyebilirsiniz.
10. Temizleme (isteğe bağlı)
Uygulamayı tutmak istemiyorsanız projenin tamamını silerek maliyet tasarrufu yapmak ve genel olarak iyi bir bulut vatandaşı olmak için kaynakları temizleyebilirsiniz:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
11. Tebrikler!
Cloud Functions, App Engine ve Cloud Run sayesinde çeşitli Web API uç noktalarını ve web ön ucunu kullanıma sunmak, kitap kitaplığını depolamak, güncellemek ve kitaplığa göz atmak için Cloud Functions, App Engine ve Cloud Run sayesinde bir dizi hizmet oluşturduk.
İşlediklerimiz
- Cloud Functions
- Cloud Firestore
- Cloud Run
- App Engine
Daha ileri gitme
Bu somut örneği daha ayrıntılı incelemek ve genişletmek isterseniz, aşağıda araştırmak isteyebileceğiniz şeylerin bir listesi verilmiştir:
- API Gateway'den yararlanarak veri içe aktarma işlevine ve REST API kapsayıcısına ortak bir API cephesi sunabilir, API'ye erişmek için API anahtarlarını yönetme gibi özellikler ekleyebilir veya API tüketicileri için hız sınırlamaları tanımlayabilirsiniz.
- REST API için test oyun alanını belgelemek ve sunmak amacıyla App Engine uygulamasında Swagger-UI düğüm modülünü dağıtın.
- Ön uçta, mevcut tarama özelliğinin ötesinde, verileri düzenlemek ve yeni kitap girişleri oluşturmak için ekstra ekranlar ekleyin. Ayrıca, Cloud Firestore veritabanını kullandığımız için değişiklikler yapıldıkça gösterilen kitap verilerini güncellemek için gerçek zamanlı özelliğinden yararlanın.