Deno Deploy ile Zamanlanmış Görev (Cron Job) Oluşturma
Sunucusuz mimarilerde zamanlanmış görevler her zaman biraz baş ağrısı olmuştur. Klasik cron job’lar için bir sunucu ayağa kaldırmak, o sunucuyu yönetmek, bakımını yapmak… Ama Deno Deploy’un Cron özelliği bu denklemi tamamen değiştiriyor. Edge’de çalışan, sıfır altyapı gerektiren, TypeScript ile yazılan zamanlanmış görevler artık birkaç satır kodla mümkün.
Bu yazıda Deno Deploy üzerinde cron job oluşturmayı, yaygın kullanım senaryolarını ve production ortamında dikkat etmeniz gereken noktaları ele alacağız.
Deno Deploy Cron Nedir?
Deno Deploy, Deno’nun resmi sunucusuz (serverless) edge platformudur. V8 isolate’leri kullanarak dünyanın farklı noktalarındaki edge lokasyonlarında kodunuzu çalıştırır. Cron özelliği ise bu platform üzerinde, herhangi bir sunucu veya harici servis olmaksızın zamanlanmış görevler tanımlamanıza olanak sağlar.
Klasik Linux cron’dan farkı şu: Bir sunucuya SSH açıp crontab -e yazmıyorsunuz. Kodunuzun içine Deno.cron() çağrısı ekliyorsunuz, deploy ediyorsunuz, iş bitiyor. Platform’un kendisi hangi saatte ne çalışacağını yönetiyor.
Şu an için Deno Deploy Cron, Beta aşamasında olmakla birlikte production kullanımına oldukça yakın bir olgunluğa ulaşmış durumda. Özellikle basit ve orta karmaşıklıktaki görevler için gayet güvenilir çalışıyor.
Temel Kurulum ve İlk Cron Job
Öncelikle yerel geliştirme ortamınızda Deno’nun kurulu olması gerekiyor.
# Deno kurulumu (Linux/macOS)
curl -fsSL https://deno.land/install.sh | sh
# Windows için
irm https://deno.land/install.ps1 | iex
# Versiyon kontrolü
deno --version
Deno Deploy ile çalışmak için deployctl aracını da kurmanız gerekiyor.
# deployctl kurulumu
deno install -gArf jsr:@deno/deployctl
# Giriş yapma
deployctl login
Şimdi en basit cron job örneğiyle başlayalım. main.ts adında bir dosya oluşturun.
// main.ts - Temel cron job örneği
Deno.serve((_req) => {
return new Response("Merhaba, Deno Deploy!");
});
Deno.cron("gunluk-rapor", "0 9 * * *", async () => {
console.log("Günlük rapor görevi başladı:", new Date().toISOString());
// Burada gerçek iş mantığınız olacak
await generateDailyReport();
console.log("Günlük rapor görevi tamamlandı.");
});
async function generateDailyReport() {
// Örnek: Bir API'ye istek atma
const response = await fetch("https://api.example.com/report/generate", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${Deno.env.get("API_TOKEN")}`,
},
body: JSON.stringify({
date: new Date().toISOString().split("T")[0],
type: "daily",
}),
});
if (!response.ok) {
throw new Error(`Rapor oluşturma başarısız: ${response.status}`);
}
const result = await response.json();
console.log("Rapor ID:", result.reportId);
}
Dikkat etmeniz gereken önemli bir nokta var: Deno.serve() çağrısı olmadan deploy ettiğiniz bir proje, Deno Deploy tarafından geçerli bir uygulama olarak kabul edilmeyebilir. O yüzden cron-only projelerinde bile basit bir HTTP handler eklemek iyi bir pratik.
Cron Söz Dizimi
Deno Deploy cron ifadeleri standart Unix cron söz dizimini kullanır. Altı alan yerine beş alanlı klasik format geçerlidir.
* * * * *
│ │ │ │ └── Haftanın günü (0-7, 0 ve 7 Pazar)
│ │ │ └──── Ay (1-12)
│ │ └────── Ayın günü (1-31)
│ └──────── Saat (0-23)
└────────── Dakika (0-59)
Yaygın kullanılan ifade örnekleri:
0: Her saat başı/15 *: Her 15 dakikada bir0 9 1-5: Hafta içi her sabah 09:000 0 1: Her ayın ilk günü gece yarısı30 6 0: Her Pazar sabahı 06:300 /6: 6 saatte bir0 9,13,17 *: Sabah 9, öğleden sonra 1 ve 5’te
Bir önemli not: Deno Deploy cron görevleri UTC saat dilimine göre çalışır. Türkiye saatiyle (UTC+3) çalışmak istiyorsanız bunu hesaba katmanız gerekiyor. Türkiye’de sabah 09:00’da çalışmasını istiyorsanız 0 6 * yazmalısınız.
Gerçek Dünya Senaryosu 1: Veritabanı Temizliği
Production sistemlerde en sık karşılaşılan cron kullanım senaryolarından biri eski kayıtların temizlenmesidir. Deno Deploy üzerinde bir PostgreSQL veritabanına bağlanarak eski log kayıtlarını temizleyen bir örnek yapalım.
// cleanup-job.ts
import { Pool } from "https://deno.land/x/[email protected]/mod.ts";
const pool = new Pool(Deno.env.get("DATABASE_URL"), 3, true);
Deno.serve((_req) => new Response("OK"));
// Her gün gece 02:00'de çalışır (UTC 23:00 = Türkiye 02:00)
Deno.cron("veritabani-temizlik", "0 23 * * *", async () => {
const client = await pool.connect();
try {
console.log("Veritabanı temizliği başladı");
// 30 günden eski log kayıtlarını sil
const result = await client.queryObject(`
DELETE FROM application_logs
WHERE created_at < NOW() - INTERVAL '30 days'
RETURNING id
`);
console.log(`${result.rowCount} log kaydı silindi`);
// Soft-delete edilmiş kullanıcı verilerini temizle
const userResult = await client.queryObject(`
DELETE FROM users
WHERE deleted_at IS NOT NULL
AND deleted_at < NOW() - INTERVAL '90 days'
RETURNING id, email
`);
console.log(`${userResult.rowCount} kullanıcı kaydı kalıcı olarak silindi`);
// Süresi dolmuş oturum tokenlarını temizle
await client.queryObject(`
DELETE FROM sessions
WHERE expires_at < NOW()
`);
console.log("Temizlik işlemi başarıyla tamamlandı");
} catch (error) {
console.error("Temizlik hatası:", error);
// Hata bildirimi gönder
await sendAlert("veritabani-temizlik", error.message);
} finally {
client.release();
}
});
async function sendAlert(jobName: string, errorMessage: string) {
const webhookUrl = Deno.env.get("SLACK_WEBHOOK_URL");
if (!webhookUrl) return;
await fetch(webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: `Cron Job Hatası: ${jobName}n${errorMessage}`,
}),
});
}
Gerçek Dünya Senaryosu 2: Önbellek Yenileme
Edge computing’de önbellek yönetimi kritik bir konu. Belirli aralıklarla dış servisten veri çekip bunu bir KV store’da saklayabilirsiniz.
// cache-warmer.ts
const kv = await Deno.openKv();
Deno.serve(async (req) => {
const url = new URL(req.url);
if (url.pathname === "/api/products") {
// Önce önbellekten dene
const cached = await kv.get(["products", "list"]);
if (cached.value) {
return new Response(JSON.stringify(cached.value), {
headers: {
"Content-Type": "application/json",
"X-Cache": "HIT"
},
});
}
// Önbellekte yoksa direkt çek
const products = await fetchProducts();
return new Response(JSON.stringify(products), {
headers: {
"Content-Type": "application/json",
"X-Cache": "MISS"
},
});
}
return new Response("Not Found", { status: 404 });
});
// Her 30 dakikada bir önbelleği yenile
Deno.cron("onbellek-yenileme", "*/30 * * * *", async () => {
console.log("Önbellek yenileme başladı:", new Date().toISOString());
try {
const products = await fetchProducts();
// 35 dakika geçerlilik süresiyle kaydet
await kv.set(["products", "list"], products, {
expireIn: 35 * 60 * 1000, // milliseconds
});
console.log(`${products.length} ürün önbelleğe alındı`);
// Kategorileri de güncelle
const categories = await fetchCategories();
await kv.set(["categories", "list"], categories, {
expireIn: 60 * 60 * 1000, // 1 saat
});
console.log("Önbellek yenileme tamamlandı");
} catch (error) {
console.error("Önbellek yenileme hatası:", error);
}
});
async function fetchProducts() {
const response = await fetch("https://api.example.com/products", {
headers: {
"Authorization": `Bearer ${Deno.env.get("API_KEY")}`,
},
});
if (!response.ok) {
throw new Error(`API hatası: ${response.status}`);
}
return await response.json();
}
async function fetchCategories() {
const response = await fetch("https://api.example.com/categories");
return await response.json();
}
Gerçek Dünya Senaryosu 3: E-posta Bildirimleri ve Raporlama
Haftalık özet e-postası göndermek çok yaygın bir kullanım senaryosu. Resend veya SendGrid gibi servislerle entegrasyon oldukça kolay.
// weekly-report.ts
Deno.serve((_req) => new Response("OK"));
// Her Pazartesi sabahı 07:00 (Türkiye saati)
Deno.cron("haftalik-ozet", "0 4 * * 1", async () => {
console.log("Haftalık özet raporu gönderiliyor...");
try {
const stats = await collectWeeklyStats();
await sendWeeklySummaryEmail(stats);
console.log("Haftalık rapor başarıyla gönderildi");
} catch (error) {
console.error("Haftalık rapor hatası:", error);
}
});
async function collectWeeklyStats() {
const endDate = new Date();
const startDate = new Date();
startDate.setDate(startDate.getDate() - 7);
const response = await fetch(
`https://analytics.example.com/api/stats?` +
`start=${startDate.toISOString()}&end=${endDate.toISOString()}`,
{
headers: {
"X-API-Key": Deno.env.get("ANALYTICS_API_KEY") ?? "",
},
}
);
return await response.json();
}
async function sendWeeklySummaryEmail(stats: Record<string, unknown>) {
const RESEND_API_KEY = Deno.env.get("RESEND_API_KEY");
const emailHtml = `
<h2>Haftalık Özet Raporu</h2>
<p>Bu hafta istatistikleri:</p>
<ul>
<li>Toplam ziyaretçi: <strong>${stats.totalVisitors}</strong></li>
<li>Yeni kullanıcı: <strong>${stats.newUsers}</strong></li>
<li>Tamamlanan sipariş: <strong>${stats.completedOrders}</strong></li>
<li>Gelir: <strong>${stats.revenue} TL</strong></li>
</ul>
`;
const response = await fetch("https://api.resend.com/emails", {
method: "POST",
headers: {
"Authorization": `Bearer ${RESEND_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "[email protected]",
to: ["[email protected]"],
subject: `Haftalık Özet - ${new Date().toLocaleDateString("tr-TR")}`,
html: emailHtml,
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`E-posta gönderilemedi: ${error}`);
}
}
Birden Fazla Cron Job Tanımlama
Tek bir deploy içinde birden fazla cron job tanımlamak mümkün ve oldukça pratik.
// multi-cron.ts
Deno.serve((_req) => new Response("Çoklu Cron Servisi Çalışıyor"));
// Her 5 dakikada sistem sağlık kontrolü
Deno.cron("saglik-kontrolu", "*/5 * * * *", async () => {
const services = [
"https://api.example.com/health",
"https://db.example.com/ping",
"https://cache.example.com/status",
];
for (const serviceUrl of services) {
try {
const start = Date.now();
const response = await fetch(serviceUrl, { signal: AbortSignal.timeout(5000) });
const duration = Date.now() - start;
if (!response.ok) {
console.warn(`Servis sorunlu: ${serviceUrl} - ${response.status}`);
await notifyOnCall(serviceUrl, response.status);
} else {
console.log(`${serviceUrl}: OK (${duration}ms)`);
}
} catch (error) {
console.error(`Servis erişilemez: ${serviceUrl}`, error.message);
await notifyOnCall(serviceUrl, "TIMEOUT");
}
}
});
// Her gece yarısı günlük istatistikleri topla
Deno.cron("gunluk-istatistik", "0 21 * * *", async () => {
// UTC 21:00 = Türkiye 00:00
console.log("Günlük istatistik toplama başladı");
await aggregateDailyStats();
});
// Her ayın 1'i ve 15'i faturaları işle
Deno.cron("fatura-isleme", "0 10 1,15 * *", async () => {
console.log("Fatura işleme başladı");
await processInvoices();
});
// Her Pazar gece yedekleme
Deno.cron("haftalik-yedekleme", "0 22 * * 0", async () => {
// UTC 22:00 Pazar = Türkiye 01:00 Pazartesi
console.log("Haftalık yedekleme başladı");
await createWeeklyBackup();
});
async function notifyOnCall(service: string, status: number | string) {
const webhookUrl = Deno.env.get("PAGERDUTY_WEBHOOK");
if (!webhookUrl) return;
await fetch(webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ service, status, timestamp: new Date().toISOString() }),
});
}
async function aggregateDailyStats() { /* implementasyon */ }
async function processInvoices() { /* implementasyon */ }
async function createWeeklyBackup() { /* implementasyon */ }
Deploy ve Ortam Değişkenleri Yönetimi
Uygulamanızı deploy ederken ortam değişkenlerini doğru yönetmek kritik.
# Direkt deploy
deployctl deploy --project=benim-cron-projem main.ts
# Ortam değişkeni ile deploy
deployctl deploy
--project=benim-cron-projem
--env=DATABASE_URL=postgresql://...
--env=API_TOKEN=secret123
main.ts
# Mevcut ortam değişkenlerini listele
deployctl env list --project=benim-cron-projem
# Ortam değişkeni ekle veya güncelle
deployctl env set DATABASE_URL="postgresql://user:pass@host/db"
--project=benim-cron-projem
# Ortam değişkeni sil
deployctl env unset ESKI_DEGISKEN --project=benim-cron-projem
Deno Deploy dashboard üzerinden de ortam değişkenlerini yönetebilirsiniz. Settings > Environment Variables bölümünden hassas bilgilerinizi güvenli bir şekilde saklayabilirsiniz.
Yerel geliştirme için .env dosyası kullanın ama bu dosyayı asla git’e eklemeyin.
# .env dosyası (geliştirme ortamı)
DATABASE_URL=postgresql://localhost:5432/mydb
API_TOKEN=dev_token_123
SLACK_WEBHOOK_URL=https://hooks.slack.com/...
# Yerel çalıştırma
deno run --allow-net --allow-env --env-file=.env main.ts
Hata Yönetimi ve İzleme
Production’da cron job’larınızın başarıyla çalışıp çalışmadığını takip etmek önemli. Deno Deploy, console.log çıktılarını dashboard üzerinden görüntülemenize olanak sağlıyor ama bunu bir adım öteye taşımak isteyebilirsiniz.
// monitored-cron.ts
Deno.serve((_req) => new Response("OK"));
async function withMonitoring(
jobName: string,
fn: () => Promise<void>
): Promise<void> {
const startTime = Date.now();
const pingUrl = Deno.env.get("HEALTHCHECK_PING_URL");
// Görev başlangıcını bildir (healthchecks.io veya benzeri)
if (pingUrl) {
await fetch(`${pingUrl}/${jobName}/start`).catch(() => {});
}
try {
await fn();
const duration = Date.now() - startTime;
console.log(`[BASARILI] ${jobName} - ${duration}ms`);
// Başarılı tamamlanmayı bildir
if (pingUrl) {
await fetch(`${pingUrl}/${jobName}`).catch(() => {});
}
} catch (error) {
const duration = Date.now() - startTime;
console.error(`[HATA] ${jobName} - ${duration}ms`, error);
// Hata durumunu bildir
if (pingUrl) {
await fetch(`${pingUrl}/${jobName}/fail`, {
method: "POST",
body: error.message,
}).catch(() => {});
}
// Slack veya Discord bildirimi
const slackWebhook = Deno.env.get("SLACK_WEBHOOK_URL");
if (slackWebhook) {
await fetch(slackWebhook, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: `Cron Hatası: *${jobName}*n```${error.message}````,
}),
}).catch(() => {});
}
}
}
Deno.cron("izlenen-gorev", "0 * * * *", () =>
withMonitoring("izlenen-gorev", async () => {
// Ana iş mantığı
await doSomeWork();
})
);
async function doSomeWork() {
// Gerçek iş
}
Dikkat Edilmesi Gereken Noktalar
Deno Deploy Cron kullanırken karşılaştığım bazı önemli noktalara değinmek istiyorum.
Çalışma süresi limiti: Cron görevleri belirli bir süre limitine tabidir. Çok uzun süren görevleri daha küçük parçalara bölmeyi veya arka plan işlemi olarak dış bir servise devre etmeyi düşünün.
İdempotency: Edge ortamında nadiren de olsa göreviniz birden fazla kez tetiklenebilir. Kodunuzun aynı işlemi birden fazla çalıştırma ihtimaline karşı güvenli (idempotent) olduğundan emin olun. Veritabanı işlemlerinde INSERT ... ON CONFLICT DO NOTHING gibi yaklaşımlar kullanın.
Cold start: Deno Deploy isolate’leri belirli süre inaktif kaldıktan sonra uyku moduna girebilir. Cron tetiklendiğinde ilk birkaç milisaniye cold start latency yaşayabilirsiniz. Bu genellikle sorun değil ama zaman hassasiyeti yüksek işlerde göz önünde bulundurun.
UTC offset: Tekrar vurgulamak istiyorum, tüm cron zamanları UTC’dir. Türkiye saatiyle çalışmak için her zaman 3 saat geri hesap yapın. Yaz saati uygulaması kalktığı için bu hesap şimdi kalıcı.
Log yönetimi: console.log çıktıları Deno Deploy dashboard’unda görünür ama bunların retention süresi sınırlıdır. Kritik görev logları için harici bir logging servisi (Datadog, Logtail, Grafana Cloud gibi) kullanmayı düşünün.
Lokal Test
Cron job’ları deploy etmeden önce lokal olarak test etmek isteyebilirsiniz.
# Cron'ları lokal olarak test etmek için deno run kullan
# --unstable-cron flag'i gerekli olabilir eski versiyonlarda
deno run --allow-net --allow-env --allow-read main.ts
# deno.json ile proje yapılandırması
cat deno.json
// deno.json
{
"tasks": {
"dev": "deno run --allow-net --allow-env --env-file=.env main.ts",
"deploy": "deployctl deploy --project=benim-projem main.ts"
}
}
Lokal geliştirmede cron’u anında tetiklemek için bir test endpoint’i ekleyebilirsiniz.
// Sadece geliştirme ortamında kullan!
Deno.serve(async (req) => {
const url = new URL(req.url);
// Manuel tetikleme endpoint'i (sadece local/dev ortamında!)
if (url.pathname === "/_trigger/daily-report" &&
Deno.env.get("ENVIRONMENT") === "development") {
await generateDailyReport();
return new Response("Görev tetiklendi");
}
return new Response("OK");
});
GitHub Actions ile CI/CD Entegrasyonu
Deploy sürecini otomatize etmek için GitHub Actions kullanabilirsiniz.
# .github/workflows/deploy.yml
name: Deploy to Deno Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Deploy to Deno Deploy
uses: denoland/deployctl@v1
with:
project: "benim-cron-projem"
entrypoint: "main.ts"
Sonuç
Deno Deploy Cron, özellikle zaten Deno veya TypeScript kullanan ekipler için cron yönetimini dramatik biçimde basitleştiriyor. Sunucu kurulumu yok, sistem yönetimi yok, crontab -e ile uğraşmak yok. Kodunuzu yazıyorsunuz, deploy ediyorsunuz, Deno Deploy gerisini hallediyor.
Özellikle şu senaryolar için çok iyi bir tercih:
- Düzenli veri senkronizasyonu ve ETL işleri
- Önbellek yenileme görevleri
- Periyodik bildirim ve rapor gönderimi
- Sistem sağlık kontrolleri
- Veritabanı bakım görevleri
Ancak saatlerce süren ağır işler, yüksek bellek gerektiren görevler veya çok hassas zamanlama gerektiren durumlar için hâlâ geleneksel bir VPS üzerinde cron veya Kubernetes CronJob daha iyi bir seçenek olabilir.
Deno Deploy’un ücretsiz planı cron görevleri için yeterince cömert. Küçük ve orta ölçekli projeler için ciddi bir altyapı maliyeti olmaksızın production kalitesinde zamanlanmış görevler çalıştırabilirsiniz. Bir dahaki seyahat planlama veya rapor sistemi projenizde klasik cron’a uzanmadan önce bu seçeneği değerlendirmenizi tavsiye ederim.
