Favori uygulamalarınızdan bazılarının gerçek zamanlı güncellemeleri nasıl gerçekleştirdiğini hiç merak ettiniz mi? Canlı spor skorları, borsa takipleri ve hatta sosyal medya bildirimleri; bunların tümü, verileri anında işlemek için olay odaklı mimariye (EDA) dayanır. EDA, her yeni bilginin anında yanıt verilmesini tetiklediği bir sohbete benziyor. Bir uygulamayı daha etkileşimli ve duyarlı kılan şey budur.
Bu izlenecek yolda, Heroku'da Apache Kafka'yı kullanarak basit, olaya dayalı bir uygulama oluşturma konusunda size rehberlik edeceğiz. Şunları ele alacağız:
Apache Kafka, EDA sistemleri oluşturmak için güçlü bir araçtır. Gerçek zamanlı veri akışlarını yönetmek için tasarlanmış açık kaynaklı bir platformdur. Heroku'da Apache Kafka, Kafka'yı hizmet olarak sağlayan bir Heroku eklentisidir. Heroku, uygulamaları dağıtmayı ve yönetmeyi oldukça kolaylaştırıyor ve son zamanlarda bunu projelerimde daha çok kullanıyorum. Kafka'yı Heroku ile birleştirmek, olaya dayalı bir uygulamayı çalıştırmak istediğinizde kurulum sürecini basitleştirir.
Bu kılavuzun sonunda, Heroku'da Apache Kafka ile EDA'nın gücünü gösteren, çalışan bir uygulamanız olacak. Başlayalım!
Koda dalmadan önce bazı temel kavramları hızlıca gözden geçirelim. Bunları anladıktan sonra takip etmeniz daha kolay olacaktır.
KafkaJS kütüphanesini kullanarak bir Node.js uygulaması oluşturacağız. Uygulamamızın nasıl çalışacağına dair kısa bir genel bakış:
Hava durumu sensörlerimiz (üreticiler) periyodik olarak sıcaklık, nem ve barometrik basınç gibi veriler üretecek ve bu olayları Apache Kafka'ya gönderecektir. Demo amacıyla veriler rastgele oluşturulacaktır.
Konuları dinleyen bir tüketicimiz olacak. Yeni bir olay alındığında verileri bir günlüğe yazacaktır.
Tüm kurulumu Heroku'ya dağıtacağız ve olayları meydana geldikçe izlemek için Heroku günlüklerini kullanacağız.
Başlamadan önce aşağıdakilere sahip olduğunuzdan emin olun:
Bu projenin tamamının kod tabanı bu GitHub deposunda mevcuttur. Kodu kopyalamaktan ve bu yazı boyunca takip etmekten çekinmeyin.
Artık temelleri ele aldığımıza göre, Heroku üzerinde Kafka kümemizi kurup oluşturmaya başlayalım.
Heroku'da her şeyi ayarlayalım. Oldukça hızlı ve kolay bir işlem.
~/project$ heroku login
~/project$ heroku create weather-eda
(Heroku uygulamama weather-eda
adını verdim ancak siz uygulamanız için benzersiz bir ad seçebilirsiniz.)
~/project$ heroku addons:create heroku-kafka:basic-0 Creating heroku-kafka:basic-0 on ⬢ weather-eda... ~$0.139/hour (max $100/month) The cluster should be available in a few minutes. Run `heroku kafka:wait` to wait until the cluster is ready. You can read more about managing Kafka at https://devcenter.heroku.com/articles/kafka-on-heroku#managing-kafka kafka-adjacent-07560 is being created in the background. The app will restart when complete... Use heroku addons:info kafka-adjacent-07560 to check creation progress Use heroku addons:docs heroku-kafka to view documentation
Heroku'daki Apache Kafka eklentisi hakkında daha fazla bilgiyi burada bulabilirsiniz. Demomuz için eklentinin Temel 0 katmanını ekliyorum. Eklentinin maliyeti 0,139$/saattir. Bu demo uygulamasını oluştururken eklentiyi bir saatten az kullandım ve sonra kapattım.
Heroku'nun Kafka'yı harekete geçirip sizin için hazırlaması birkaç dakika sürer. Çok yakında şunu göreceksiniz:
~/project$ heroku addons:info kafka-adjacent-07560 === kafka-adjacent-07560 Attachments: weather-eda::KAFKA Installed at: Mon May 27 2024 11:44:37 GMT-0700 (Mountain Standard Time) Max Price: $100/month Owning app: weather-eda Plan: heroku-kafka:basic-0 Price: ~$0.139/hour State: created
Kafka kümemiz açıldığında kimlik bilgilerini ve diğer yapılandırmaları almamız gerekecek. Heroku, uygulamamız için çeşitli yapılandırma değişkenleri oluşturur ve bunları yeni oluşturulan Kafka kümesindeki bilgilerle doldurur. Aşağıdakileri çalıştırarak tüm bu yapılandırma değişkenlerini görebiliriz:
~/project$ heroku config === weather-eda Config Vars KAFKA_CLIENT_CERT: -----BEGIN CERTIFICATE----- MIIDQzCCAiugAwIBAgIBADANBgkqhkiG9w0BAQsFADAyMTAwLgYDVQQDDCdjYS1h ... -----END CERTIFICATE----- KAFKA_CLIENT_CERT_KEY: -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAsgv1oBiF4Az/IQsepHSh5pceL0XLy0uEAokD7ety9J0PTjj3 ... -----END RSA PRIVATE KEY----- KAFKA_PREFIX: columbia-68051. KAFKA_TRUSTED_CERT: -----BEGIN CERTIFICATE----- MIIDfzCCAmegAwIBAgIBADANBgkqhkiG9w0BAQsFADAyMTAwLgYDVQQDDCdjYS1h ... F+f3juViDqm4eLCZBAdoK/DnI4fFrNH3YzhAPdhoHOa8wi4= -----END CERTIFICATE----- KAFKA_URL: kafka+ssl://ec2-18-233-140-74.compute-1.amazonaws.com:9096,kafka+ssl://ec2-18-208-61-56.compute-1.amazonaws.com:9096...kafka+ssl://ec2-34-203-24-91.compute-1.amazonaws.com:9096
Gördüğünüz gibi birçok yapılandırma değişkenimiz var. Proje kök klasörümüzde tüm bu config var değerlerinin bulunduğu .env
adında bir dosya olmasını isteyeceğiz. Bunu yapmak için aşağıdaki komutu çalıştırmamız yeterlidir:
~/project$ heroku config --shell > .env
.env
dosyamız şuna benzer:
KAFKA_CLIENT_CERT="-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----" KAFKA_CLIENT_CERT_KEY="-----BEGIN RSA PRIVATE KEY----- ... -----END RSA PRIVATE KEY-----" KAFKA_PREFIX="columbia-68051." KAFKA_TRUSTED_CERT="-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----" KAFKA_URL="kafka+ssl://ec2-18-233-140-74.compute-1.amazonaws.com:9096,kafka+ssl://ec2-18-208-61-56.compute-1.amazonaws.com:9096...kafka+ssl://ec2-34-203-24-91.compute-1.amazonaws.com:9096"
Ayrıca .gitignore dosyamıza .env dosyasını da eklemeye dikkat ediyoruz. Bu hassas verileri depomuza dahil etmek istemeyiz.
Heroku CLI, Kafka ile ilgili komutlarla birlikte kutudan çıkar çıkmaz gelmez. Kafka kullandığımız için CLI eklentisini kurmamız gerekecek.
~/project$ heroku plugins:install heroku-kafka Installing plugin heroku-kafka... installed v2.12.0
Artık Kafka kümemizi CLI üzerinden yönetebiliriz.
~/project$ heroku kafka:info === KAFKA_URL Plan: heroku-kafka:basic-0 Status: available Version: 2.8.2 Created: 2024-05-27T18:44:38.023+00:00 Topics: [··········] 0 / 40 topics, see heroku kafka:topics Prefix: columbia-68051. Partitions: [··········] 0 / 240 partition replicas (partitions × replication factor) Messages: 0 messages/s Traffic: 0 bytes/s in / 0 bytes/s out Data Size: [··········] 0 bytes / 4.00 GB (0.00%) Add-on: kafka-adjacent-07560 ~/project$ heroku kafka:topics === Kafka Topics on KAFKA_URL No topics found on this Kafka cluster. Use heroku kafka:topics:create to create a topic (limit 40)
Akıl sağlığı kontrolü olarak Kafka kümemizle biraz oynayalım. Bir konu oluşturarak başlıyoruz.
~/project$ heroku kafka:topics:create test-topic-01 Creating topic test-topic-01 with compaction disabled and retention time 1 day on kafka-adjacent-07560... done Use `heroku kafka:topics:info test-topic-01` to monitor your topic. Your topic is using the prefix columbia-68051.. ~/project$ heroku kafka:topics:info test-topic-01 ▸ topic test-topic-01 is not available yet
Bir dakika kadar sonra konumuz hazır hale geliyor.
~/project$ heroku kafka:topics:info test-topic-01 === kafka-adjacent-07560 :: test-topic-01 Topic Prefix: columbia-68051. Producers: 0 messages/second (0 bytes/second) total Consumers: 0 bytes/second total Partitions: 8 partitions Replication Factor: 3 Compaction: Compaction is disabled for test-topic-01 Retention: 24 hours
Daha sonra, bu terminal penceresinde, bu konuyu takip ederek dinleyen bir tüketici gibi davranacağız.
~/project$ heroku kafka:topics:tail test-topic-01
Buradan terminal, konuyla ilgili yayınlanan etkinlikleri bekler.
Ayrı bir terminal penceresinde yapımcı olarak hareket edeceğiz ve konuyla ilgili bazı mesajlar yayınlayacağız.
~/project$ heroku kafka:topics:write test-topic-01 "hello world!"
Tüketicimizin terminal penceresine döndüğümüzde şunu görüyoruz:
~/project$ heroku kafka:topics:tail test-topic-01 test-topic-01 0 0 12 hello world!
Harika! Kafka kümemizdeki bir konuya ilişkin etkinliği başarıyla ürettik ve tükettik. Node.js uygulamamıza geçmeye hazırız. Oyun alanımızı düzenli tutmak için bu test konusunu yok edelim.
~/project$ heroku kafka:topics:destroy test-topic-01 ▸ This command will affect the cluster: kafka-adjacent-07560, which is on weather-eda ▸ To proceed, type weather-eda or re-run this command with --confirm weather-eda > weather-eda Deleting topic test-topic-01... done Your topic has been marked for deletion, and will be removed from the cluster shortly ~/project$ heroku kafka:topics === Kafka Topics on KAFKA_URL No topics found on this Kafka cluster. Use heroku kafka:topics:create to create a topic (limit 40).
Uygulamamızın Kafka'yı kullanmasına hazırlanmak için iki şey oluşturmamız gerekecek: bir konu ve bir tüketici grubu.
Uygulamamızın kullanacağı konuyu oluşturalım.
~/project$ heroku kafka:topics:create weather-data
Daha sonra uygulamamızın tüketicisinin parçası olacağı tüketici grubunu oluşturacağız:
~/project$ heroku kafka:consumer-groups:create weather-consumers
Node.js uygulamamızı oluşturmaya hazırız!
Yeni bir proje başlatalım ve bağımlılıklarımızı kuralım.
~/project$ npm init -y ~/project$ npm install kafkajs dotenv @faker-js/faker pino pino-pretty
Projemizde çalışan iki süreç olacak:
Konuya abone olan ve yayınlanan tüm etkinlikleri günlüğe kaydeden consumer.js
.
producer.js
, konuyla ilgili bazı rastgele hava durumu verilerini birkaç saniyede bir yayınlayacak.
Bu süreçlerin her ikisinin de Kafka kümemize bağlanmak için KafkaJS'yi kullanması gerekecek, bu nedenle kodumuzu yeniden kullanılabilir hale getirmek için modülerleştireceğiz.
Proje src
klasöründe kafka.js
adında bir dosya oluşturuyoruz. Şuna benziyor:
const { Kafka } = require('kafkajs'); const BROKER_URLS = process.env.KAFKA_URL.split(',').map(uri => uri.replace('kafka+ssl://','' )) const TOPIC = `${process.env.KAFKA_PREFIX}weather-data` const CONSUMER_GROUP = `${process.env.KAFKA_PREFIX}weather-consumers` const kafka = new Kafka({ clientId: 'weather-eda-app-nodejs-client', brokers: BROKER_URLS, ssl: { rejectUnauthorized: false, ca: process.env.KAFKA_TRUSTED_CERT, key: process.env.KAFKA_CLIENT_CERT_KEY, cert: process.env.KAFKA_CLIENT_CERT, }, }) const producer = async () => { const p = kafka.producer() await p.connect() return p; } const consumer = async () => { const c = kafka.consumer({ groupId: CONSUMER_GROUP, sessionTimeout: 30000 }) await c.connect() await c.subscribe({ topics: [TOPIC] }); return c; } module.exports = { producer, consumer, topic: TOPIC, groupId: CONSUMER_GROUP };
Bu dosyada yeni bir Kafka istemcisi oluşturarak başlıyoruz. Bu, Kafka aracıları için .env
dosyamızdaki KAFKA_URL
değişkeninden (başlangıçta heroku config çağrılmasından gelen) ayrıştırabildiğimiz URL'leri gerektirir. Bağlantı girişimini doğrulamak için KAFKA_TRUSTED_CERT
, KAFKA_CLIENT_CERT_KEY
ve KAFKA_CLIENT_CERT
sağlamamız gerekiyor.
Daha sonra Kafka müşterimizden bir producer
ve consumer
yaratıyoruz ve tüketicilerimizi weather-data
konusuna abone olduklarından emin oluyoruz.
kafka.js
konumuzun ve tüketici grubu adımızın başına KAFKA_PREFIX
eklediğimize dikkat edin. Heroku'da Apache Kafka için çok kiracılı bir Kafka planı olan Basic 0 planını kullanıyoruz. Bu, KAFKA_PREFIX
ile çalıştığımız anlamına gelir. Konumuza weather-data
ve tüketici grubumuz weather-consumers
adını vermiş olsak da, çok kiracılı Kafka kümemizdeki gerçek adlarının başına KAFKA_PREFIX
eklenmiş olmalıdır (benzersiz olduklarından emin olmak için).
Yani teknik olarak demomuz için asıl konu adı weather-data
değil, columbia-68051.weather-data
. (Aynı şekilde tüketici grubu adı için de.)
Şimdi hava durumu sensörü üreticimiz olarak görev yapacak arka plan sürecimizi oluşturalım. Proje kök klasörümüzde producer.js
adında bir dosyamız var. Şuna benziyor:
require('dotenv').config(); const kafka = require('./src/kafka.js'); const { faker } = require('@faker-js/faker'); const SENSORS = ['sensor01','sensor02','sensor03','sensor04','sensor05']; const MAX_DELAY_MS = 20000; const READINGS = ['temperature','humidity','barometric_pressure']; const MAX_TEMP = 130; const MIN_PRESSURE = 2910; const PRESSURE_RANGE = 160; const getRandom = (arr) => arr[faker.number.int(arr.length - 1)]; const getRandomReading = { temperature: () => faker.number.int(MAX_TEMP) + (faker.number.int(100) / 100), humidity: () => faker.number.int(100) / 100, barometric_pressure: () => (MIN_PRESSURE + faker.number.int(PRESSURE_RANGE)) / 100 }; const sleep = (ms) => { return new Promise((resolve) => { setTimeout(resolve, ms); }); }; (async () => { const producer = await kafka.producer() while(true) { const sensor = getRandom(SENSORS) const reading = getRandom(READINGS) const value = getRandomReading[reading]() const data = { reading, value } await producer.send({ topic: kafka.topic, messages: [{ key: sensor, value: JSON.stringify(data) }] }) await sleep(faker.number.int(MAX_DELAY_MS)) } })()
Dosyadaki kodun büyük bir kısmı rastgele değerler üretmekle ilgilidir. Önemli kısımları vurgulayacağım:
SENSORS
bulunur.
temperature
, humidity
veya barometric_pressure
. getRandomReading
nesnesi, bu okumaların her biri için makul bir karşılık gelen değer oluşturmak üzere bir işleve sahiptir.
while
döngüsüne sahip bir async
işlev olarak çalışır.
while
döngüsünde şunları yaparız:
sensor
seçin.reading
seçin.value
oluşturun.producer.send
arayın. sensor
olayın key
görevi görürken reading
ve value
olay mesajını oluşturacaktır. consumer.js
arka plan işlemi oldukça basittir.
require('dotenv').config(); const logger = require('./src/logger.js'); const kafka = require('./src/kafka.js'); (async () => { const consumer = await kafka.consumer() await consumer.run({ eachMessage: async ({ topic, partition, message }) => { const sensorId = message.key.toString() const messageObj = JSON.parse(message.value.toString()) const logMessage = { sensorId } logMessage[messageObj.reading] = messageObj.value logger.info(logMessage) } }) })()
consumer
zaten weather-data
konusuna abone oldu. consumer.run
adını veriyoruz ve ardından eachMessage
için bir işleyici ayarlıyoruz. Kafka consumer
bir mesaj gönderdiğinde mesajı günlüğe kaydeder. Hepsi bu kadar.
Procfile
package.json
dosyasına üretici ve tüketici arka plan süreçlerimizi başlatan birkaç scripts
eklememiz gerekiyor. Dosya artık aşağıdakileri içermelidir:
... "scripts": { "start": "echo 'do nothing'", "start:consumer": "node consumer.js", "start:producer": "node producer.js" }, ...
Önemli olanlar start:consumer
ve start:producer
. Ancak Heroku oluşturucusu onun orada olmasını beklediği için (anlamlı bir şey yapmasa da) dosyamızda start
devam ediyoruz.
Daha sonra, Heroku'ya, Heroku uygulamamız için ihtiyaç duyduğumuz çeşitli işçileri nasıl başlatacağımızı anlatacak bir Procfile
oluşturuyoruz. Projemizin kök klasöründe Procfile
şu şekilde görünmelidir:
consumer_worker: npm run start:consumer producer_worker: npm run start:producer
Oldukça basit, değil mi? consumer_worker
adında bir arka plan süreç çalışanımız ve producer_worker
adında bir başka çalışanımız olacak. Bir web uygulaması için genellikle Procfile
göreceğiniz gibi bir web
çalışanımızın olmadığını fark edeceksiniz. Heroku uygulamamız için sadece iki arka plan çalışanına ihtiyacımız var. web
ihtiyacımız yok.
Bununla birlikte tüm kodumuz ayarlandı. Tüm kodumuzu repoya kaydettik ve dağıtıma hazırız.
~/project$ git push heroku main … remote: -----> Build succeeded! … remote: -----> Compressing... remote: Done: 48.6M remote: -----> Launching... … remote: Verifying deploy... done
Konuşlandırıldıktan sonra dinamolarımızı doğru şekilde ölçeklendirdiğimizden emin olmak istiyoruz. Bir web süreci için bir dinamoya ihtiyacımız yok, ancak hem consumer_worker
hem de producer_worker
için bir dinamoya ihtiyacımız olacak. Bu süreçleri ihtiyaçlarımıza göre ayarlamak için aşağıdaki komutu çalıştırıyoruz.
~/project$ heroku ps:scale web=0 consumer_worker=1 producer_worker=1 Scaling dynos... done, now running producer_worker at 1:Eco, consumer_worker at 1:Eco, web at 0:Eco
Şimdi her şey çalışır durumda olmalı. Perde arkasında, producer_worker
Kafka kümesine bağlanmalı ve ardından birkaç saniyede bir hava durumu sensörü verilerini yayınlamaya başlamalıdır. Daha sonra consumer_worker
Kafka kümesine bağlanmalı ve abone olduğu konudan aldığı mesajları kaydetmelidir.
consumer_worker
ne yaptığını görmek için Heroku günlüklerimize bakabiliriz.
~/project$ heroku logs --tail … heroku[producer_worker.1]: Starting process with command `npm run start:producer` heroku[producer_worker.1]: State changed from starting to up app[producer_worker.1]: app[producer_worker.1]: > [email protected] start:producer app[producer_worker.1]: > node producer.js app[producer_worker.1]: … heroku[consumer_worker.1]: Starting process with command `npm run start:consumer` heroku[consumer_worker.1]: State changed from starting to up app[consumer_worker.1]: app[consumer_worker.1]: > [email protected] start:consumer app[consumer_worker.1]: > node consumer.js app[consumer_worker.1]: app[consumer_worker.1]: {"level":"INFO","timestamp":"2024-05-28T02:31:20.660Z","logger":"kafkajs","message":"[Consumer] Starting","groupId":"columbia-68051.weather-consumers"} app[consumer_worker.1]: {"level":"INFO","timestamp":"2024-05-28T02:31:23.702Z","logger":"kafkajs","message":"[ConsumerGroup] Consumer has joined the group","groupId":"columbia-68051.weather-consumers","memberId":"weather-eda-app-nodejs-client-3ee5d1fa-eba9-4b59-826c-d3b924a6e4e4","leaderId":"weather-eda-app-nodejs-client-3ee5d1fa-eba9-4b59-826c-d3b924a6e4e4","isLeader":true,"memberAssignment":{"columbia-68051.test-topic-1":[0,1,2,3,4,5,6,7]},"groupProtocol":"RoundRobinAssigner","duration":3041} app[consumer_worker.1]: [2024-05-28 02:31:23.755 +0000] INFO (21): {"sensorId":"sensor01","temperature":87.84} app[consumer_worker.1]: [2024-05-28 02:31:23.764 +0000] INFO (21): {"sensorId":"sensor01","humidity":0.3} app[consumer_worker.1]: [2024-05-28 02:31:23.777 +0000] INFO (21): {"sensorId":"sensor03","temperature":22.11} app[consumer_worker.1]: [2024-05-28 02:31:37.773 +0000] INFO (21): {"sensorId":"sensor01","barometric_pressure":29.71} app[consumer_worker.1]: [2024-05-28 02:31:54.495 +0000] INFO (21): {"sensorId":"sensor05","barometric_pressure":29.55} app[consumer_worker.1]: [2024-05-28 02:32:02.629 +0000] INFO (21): {"sensorId":"sensor04","temperature":90.58} app[consumer_worker.1]: [2024-05-28 02:32:03.995 +0000] INFO (21): {"sensorId":"sensor02","barometric_pressure":29.25} app[consumer_worker.1]: [2024-05-28 02:32:12.688 +0000] INFO (21): {"sensorId":"sensor04","humidity":0.1} app[consumer_worker.1]: [2024-05-28 02:32:32.127 +0000] INFO (21): {"sensorId":"sensor01","humidity":0.34} app[consumer_worker.1]: [2024-05-28 02:32:32.851 +0000] INFO (21): {"sensorId":"sensor02","humidity":0.61} app[consumer_worker.1]: [2024-05-28 02:32:37.200 +0000] INFO (21): {"sensorId":"sensor01","barometric_pressure":30.36} app[consumer_worker.1]: [2024-05-28 02:32:50.388 +0000] INFO (21): {"sensorId":"sensor03","temperature":104.55}
İşe yarıyor! Üreticimizin periyodik olarak Kafka'ya mesaj yayınladığını biliyoruz çünkü tüketicimiz bu mesajları alıyor ve ardından günlüğe kaydediyor.
Elbette daha büyük bir EDA uygulamasında her sensör bir üreticidir. Çeşitli amaçlarla birden fazla konuda yayın yapabileceği gibi hepsi aynı konuda yayın da yapabilirler. Ve tüketiciniz birden fazla konuya abone olabilir. Ayrıca demo uygulamamızda tüketicilerimiz eachMessage
Mesajda çok fazla mesaj gönderdi; ancak bir EDA uygulamasında tüketici üçüncü taraf bir API'yi arayarak, SMS bildirimi göndererek veya bir veritabanını sorgulayarak yanıt verebilir.
Artık olaylar, konular, üreticiler ve tüketiciler hakkında temel bir anlayışa sahip olduğunuza ve Kafka ile nasıl çalışacağınızı bildiğinize göre, daha karmaşık iş kullanım durumlarını karşılamak için kendi EDA uygulamalarınızı tasarlamaya ve oluşturmaya başlayabilirsiniz.
EDA oldukça güçlüdür; kolay ölçeklenebilirlik ve gerçek zamanlı veri işleme gibi temel özelliklerin keyfini çıkarırken sistemlerinizi ayırabilirsiniz. EDA için Kafka, yüksek verimli veri akışlarını kolaylıkla yönetmenize yardımcı olan önemli bir araçtır. Heroku'da Apache Kafka'yı kullanmak hızlı bir şekilde başlamanıza yardımcı olur. Yönetilen bir hizmet olduğundan Kafka küme yönetiminin karmaşık kısımları hakkında endişelenmenize gerek yoktur. Yalnızca uygulamalarınızı oluşturmaya odaklanabilirsiniz.
Buradan itibaren deneme ve prototip yapma zamanı geldi. Hangi kullanım durumlarının EDA'ya uygun olduğunu belirleyin. Dalın, Heroku'da test edin ve muhteşem bir şey oluşturun. Mutlu kodlama!