Bir Yazılım Projesine Kod Yazarak Başlamayın: Fikirden Production'a Yazılım Geliştirme Süreci
Bir yazılım projesinin ilk adımı kod yazmak değil, çözülecek problemi anlamaktır. Bu yazıda bir projeyi fikir aşamasından production ortamına götürürken izlenmesi gereken adımları — problem tanımından MVP kapsamına, veri modelinden observability'ye kadar — adım adım inceliyoruz.

Bir Yazılım Projesine Kod Yazarak Başlamayın: Fikirden Production’a Geliştirme Süreci
Yeni bir yazılım projesine başladığınızda ilk yaptığınız şey nedir?
Repository oluşturmak mı?
Framework seçmek mi?
Database bağlantısını hazırlamak mı?
İlk endpoint’i yazmak mı?
Yazılım geliştiriciler olarak kod yazmayı seviyoruz. Bu yüzden yeni bir fikir ortaya çıktığında mümkün olduğunca hızlı şekilde geliştirmeye başlamak istiyoruz.
Bir backend projesiyse klasör yapısını oluşturuyoruz.
Bir mobil uygulamaysa navigation yapısını hazırlıyoruz.
Database tablolarını oluşturup migration almaya başlıyoruz.
Birkaç gün içerisinde çalışan bir şey ortaya çıkıyor.
Sonra proje büyüyor.
Ve şu sorular gelmeye başlıyor:
Bu özellik gerçekten gerekli miydi?
Bu tabloyu neden böyle tasarladık?
Frontend neden bu veriyi farklı bir formatta istiyor?
Kullanıcı bu işlemi neden üç farklı ekrandan yapmak zorunda?
Bu sistemi kim kullanacak?
İşin kötü tarafı şu:
Bu soruların büyük bölümü kod problemi değildir.
Bunlar, geliştirme sürecinin yanlış noktadan başlamasının sonucudur.
Çünkü bir yazılım projesinin ilk adımı kod yazmak değildir.
İlk adım, çözülecek problemi anlamaktır.
Bu yazıda örnek bir teknik servis yönetim sistemi üzerinden bir yazılım projesinin fikir aşamasından production ortamına kadar nasıl ele alınabileceğini adım adım inceleyeceğiz.
1. Önce Problemi Tanımlayın
Bir projeye başlamadan önce cevaplamamız gereken ilk soru oldukça basittir:
Hangi problemi çözüyoruz?
Şu cümleler problem tanımı değildir:
Mobil uygulama geliştireceğiz.
Mikroservis mimarisi kullanacağız.
Node.js ile backend yazacağız.
Yapay zekâ destekli olacak.
Bunların tamamı çözüm veya teknoloji tercihleridir.
Gerçek bir problem tanımı kullanıcı ve mevcut süreç üzerinden yapılmalıdır.
Bir teknik servis şirketini düşünelim.
Şirket onlarca müşteriye bakım ve arıza hizmeti veriyor.
Müşteri bir cihazında problem yaşadığında şirketi telefonla arıyor veya WhatsApp üzerinden mesaj gönderiyor.
Operasyon personeli gelen talebi bir Excel dosyasına ekliyor.
Sonra uygun bir teknisyen telefonla aranıyor.
Teknisyene müşterinin adresi ve arıza bilgisi gönderiliyor.
Teknisyen işlemi tamamladıktan sonra servis formu dolduruyor.
Yönetici gün sonunda hangi işlerin tamamlandığını öğrenmek için tekrar personelleri arıyor.
Buradaki problem şudur:
Teknik servis talepleri telefon, WhatsApp ve Excel üzerinden yönetildiği için iş emirleri kaybolabiliyor, teknisyen atamaları gecikiyor ve yöneticiler saha operasyonunu anlık olarak takip edemiyor.
Şimdi elimizde gerçek bir problem var.
Henüz React, .NET, Node.js veya PostgreSQL konuşmadık.
Çünkü teknoloji seçmek için henüz erken.
Önce problem.
Sonra çözüm.
En son teknoloji.
2. Kullanıcıları Belirleyin
Bir yazılımın kim tarafından kullanılacağı belli değilse doğru bir kullanıcı deneyimi ve doğru bir sistem mimarisi oluşturmak oldukça zordur.
Örnek teknik servis sistemimizde farklı kullanıcı tipleri olabilir.
Müşteri
Müşteri:
Kayıtlı cihazlarını görüntülemek,
Yeni arıza talebi oluşturmak,
Talebinin durumunu takip etmek,
Geçmiş servis raporlarını incelemek
isteyebilir.
Operasyon Personeli
Operasyon personeli:
Gelen servis taleplerini görmek,
Talebi değerlendirmek,
İş emri oluşturmak,
Uygun teknisyeni atamak,
Açık işleri takip etmek
isteyebilir.
Saha Teknisyeni
Teknisyen:
Kendisine atanan görevleri görmek,
Müşteri adresine ulaşmak,
Görev durumunu değiştirmek,
Kullanılan parçaları sisteme girmek,
Servis notu eklemek,
İş emrini tamamlamak
isteyebilir.
Teknik Servis Yöneticisi
Yönetici:
Açık iş emirlerini görüntülemek,
Geciken işleri görmek,
Teknisyen performansını incelemek,
Ortalama servis sürelerini ölçmek,
SLA ihlallerini takip etmek
isteyebilir.
Burada önemli bir nokta vardır.
Bu kullanıcıların tamamı aynı sistemi kullansa bile aynı sistemi aynı şekilde kullanmaz.
Bir teknisyenin ihtiyacı sade ve hızlı bir mobil ekran olabilir.
Yöneticinin ihtiyacı ise grafikler, filtreler ve detaylı raporlardır.
Kullanıcıları doğru tanımlamazsak teknik olarak çalışan ancak kullanılması zor bir ürün geliştirebiliriz.
3. Gereksinimleri Çıkarın
Problem ve kullanıcılar belirlendikten sonra sistemin ne yapması gerektiğini tanımlamamız gerekir.
Gereksinimleri iki ana grupta değerlendirebiliriz.
Fonksiyonel Gereksinimler
Fonksiyonel gereksinimler sistemin hangi işlemleri yapacağını belirtir.
Örneğin:
Müşteri sisteme kayıt olabilir.
Müşteri cihazlarını görüntüleyebilir.
Müşteri arıza talebi oluşturabilir.
Operasyon personeli servis talebini iş emrine dönüştürebilir.
İş emrine teknisyen atanabilir.
Teknisyen görev durumunu güncelleyebilir.
Teknisyen kullanılan parçaları kaydedebilir.
Servis tamamlandığında servis raporu oluşturulabilir.
Yönetici açık ve tamamlanan işleri görüntüleyebilir.
Bu maddeler sistemin davranışını açıklar.
Fonksiyonel Olmayan Gereksinimler
Fonksiyonel olmayan gereksinimler ise sistemin nasıl çalışması gerektiğini tanımlar.
Örneğin:
Kritik API endpoint’lerinin yanıt süresi 500 ms altında olmalıdır.
İş emri durum değişiklikleri kaybolmamalıdır.
Kullanıcı işlemleri yetkilendirme kontrolünden geçmelidir.
Kritik işlemler loglanmalıdır.
Sistem yedeklenebilir olmalıdır.
Servis raporları sonradan yetkisiz şekilde değiştirilememelidir.
Aynı iş emrine yanlışlıkla birden fazla aktif teknisyen ataması yapılmamalıdır.
Birçok projede fonksiyonel gereksinimler konuşulur.
Ancak performans, güvenlik, veri bütünlüğü ve izlenebilirlik gibi konular geliştirme sürecinin sonuna bırakılır.
Production problemleri de genellikle burada başlar.
4. MVP Kapsamını Belirleyin
Yeni bir ürün geliştirirken özellik üretmek oldukça kolaydır.
Teknik servis sistemi için bir toplantı yaptığımızı düşünelim.
Birisi şöyle diyebilir:
Canlı teknisyen konumu ekleyelim.
Bir başkası:
Yapay zekâ geçmiş arızalara göre problem tahmini yapsın.
Başka biri:
Müşteri teknisyenle uygulama içinden mesajlaşsın.
Sonra:
Video görüşme de ekleyelim.
Stok yönetimi de olsun.
Muhasebe entegrasyonu yapalım.
Otomatik faturalandırma ekleyelim.
Ortaya devasa bir proje çıkar.
Henüz tek bir gerçek kullanıcı sistemi kullanmamışken aylarca özellik geliştirilmeye başlanır.
MVP’nin amacı kötü veya eksik ürün geliştirmek değildir.
MVP’nin amacı ürünün temel varsayımını mümkün olan en düşük maliyetle test etmektir.
Teknik servis sistemimiz için temel problem şuydu:
Servis taleplerinin ve saha operasyonunun merkezi olarak yönetilememesi.
O halde ilk sürümde şu özellikler yeterli olabilir:
Müşteri yönetimi
Cihaz yönetimi
Servis talebi oluşturma
İş emri oluşturma
Teknisyen atama
İş emri durum yönetimi
Servis raporu oluşturma
Temel yönetim paneli
Canlı konum?
Sonra.
Yapay zekâ?
Sonra.
Mesajlaşma?
Gerçekten ihtiyaç varsa sonra.
Bir özellik için şu soruyu sormak oldukça faydalıdır:
Bu özellik olmadan ürün temel problemi hâlâ çözebiliyor mu?
Cevap evetse o özellik büyük ihtimalle MVP için zorunlu değildir.
5. Kullanıcı Akışlarını Oluşturun
Artık sistemde hangi kullanıcıların bulunduğunu ve temel özellikleri biliyoruz.
Kod yazmadan önce kullanıcı akışlarını oluşturabiliriz.
Örneğin temel servis süreci şöyle olabilir:
Müşteri arıza bildirir
↓
Servis talebi oluşturulur
↓
Operasyon personeli talebi inceler
↓
İş emri oluşturulur
↓
Teknisyen atanır
↓
Teknisyen görevi kabul eder
↓
Teknisyen müşteriye gider
↓
Servis işlemini gerçekleştirir
↓
Kullanılan parçaları sisteme girer
↓
Servis raporu oluşturur
↓
İş emri tamamlanır
Bu basit akış bile birçok soruyu ortaya çıkarır.
Teknisyen görevi reddedebilir mi?
Aynı iş emrine iki teknisyen atanabilir mi?
Teknisyen görevi kabul ettikten sonra başka bir teknisyene aktarılabilir mi?
Müşteri servis talebini iptal edebilir mi?
Tamamlanan iş emri tekrar açılabilir mi?
Parça stoğu yoksa ne olacak?
Bu soruları kodlama sırasında keşfetmek yerine daha erken aşamada konuşmak ciddi zaman kazandırır.
User flow sadece tasarım ekibinin hazırladığı bir doküman değildir.
Backend geliştiricisinin bile bu akışları anlaması gerekir.
Çünkü database tasarımı ve API yapısı doğrudan kullanıcı akışlarından etkilenir.
6. İş Kurallarını Açık Şekilde Tanımlayın
Birçok projede ekranlar tasarlanır, endpoint listeleri hazırlanır ancak iş kuralları açık şekilde yazılmaz.
Örneğin:
Teknisyen iş emrini tamamlayabilir.
Güzel.
Peki hangi koşullarda?
Servis raporu oluşturmadan tamamlayabilir mi?
Kullanılan parça varsa parça bilgisi girmeden tamamlayabilir mi?
Başka bir teknisyene atanmış işi tamamlayabilir mi?
İptal edilmiş iş emrini tamamlayabilir mi?
Daha açık bir iş kuralı şöyle olabilir:
Bir iş emri yalnızca kendisine atanmış teknisyen tarafından tamamlanabilir. İş emrinin durumu
IN_PROGRESSolmalıdır ve tamamlanmadan önce servis raporu oluşturulmalıdır.
Şimdi elimizde test edilebilir bir iş kuralı vardır.
Örneğin:
Given:
İş emri IN_PROGRESS durumunda
And:
Technician A iş emrine atanmış
When:
Technician B iş emrini tamamlamaya çalışır
Then:
WORK_ORDER_ACCESS_DENIED hatası dönmelidir
İş kuralları açık değilse kurallar yavaş yavaş controller’ların, frontend condition’larının ve database sorgularının içine dağılır.
Bir süre sonra aynı kural üç farklı yerde farklı şekilde uygulanmaya başlar.
7. Teknik Gereksinimleri Analiz Edin
Artık teknoloji konuşmaya başlayabiliriz.
Ancak soru şu olmamalıdır:
En popüler teknoloji hangisi?
Doğru soru şudur:
Bu sistemin ihtiyaçlarını hangi teknoloji doğru şekilde karşılar?
Teknik servis sistemimizi değerlendirelim.
Sistemde:
Web yönetim paneli bulunacak.
Teknisyenler mobil cihaz kullanacak.
İlişkisel veri yoğun olacak.
İş emri durumları takip edilecek.
Raporlama yapılacak.
Bildirim ihtiyacı olacak.
İlk aşamada çok büyük bir kullanıcı hacmi beklenmiyor.
Burada örneğin şöyle bir teknoloji seti tercih edilebilir:
Web:
React veya Next.js
Mobile:
React Native
Backend:
Node.js veya .NET
Database:
PostgreSQL
Cache:
Redis
Object Storage:
S3 uyumlu storage
Background Jobs:
BullMQ, Hangfire veya benzeri bir yapı
Ancak bu teknolojiler projenin değişmez doğruları değildir.
Ekibin .NET konusunda güçlü olması durumunda .NET tercih etmek oldukça mantıklıdır.
Küçük ve hızlı hareket eden bir ekip Node.js tercih edebilir.
Buradaki temel prensip şudur:
Teknoloji probleme hizmet etmelidir.
Problem teknolojiye uydurulmamalıdır.
8. Sistem Mimarisini Tasarlayın
Teknik gereksinimler belirlendikten sonra sistemin ana bileşenleri oluşturulabilir.
Örneğin:
Mobile Application
↓
API
↓
Application Layer
↓
Domain / Business Rules
↓
Data Access
↓
PostgreSQL
Sistemin yanında şu bileşenler de olabilir:
Redis
Background Worker
Object Storage
Notification Service
Burada oldukça yaygın bir hata vardır:
Daha ilk günden sistemi mikroservislere bölmek.
Şöyle bir yapı düşünelim:
Customer Service
Device Service
Work Order Service
Technician Service
Inventory Service
Notification Service
Report Service
Her servisin:
Ayrı repository’si,
Ayrı database’i,
Ayrı deployment süreci,
Ayrı log sistemi
olduğunu düşünün.
Üç kişilik bir ekip artık bir özellik geliştirmek yerine dağıtık sistem problemleri çözmeye başlar.
Bir iş emri oluşturmak için Customer Service’e gidilir.
Sonra Device Service çağrılır.
Teknisyen atamak için Technician Service çağrılır.
Bir servis cevap vermediğinde retry mekanizması gerekir.
Duplicate message problemi ortaya çıkar.
Distributed tracing gerekir.
Event versioning konuşulmaya başlanır.
Henüz sistemde 100 aktif kullanıcı yoktur.
Bu nedenle birçok proje için modular monolith oldukça güçlü bir başlangıç olabilir.
Örneğin:
modules/
customer/
device/
service-request/
work-order/
technician/
inventory/
report/
Sistem tek deployment olabilir.
Ancak modüller birbirinden mantıksal olarak ayrılır.
İleride gerçekten ihtiyaç oluşursa belirli bir modül ayrı bir servise dönüştürülebilir.
Mimari geleceği düşünmelidir.
Ancak hayali bir geleceği düşünürken bugünkü geliştirme hızını öldürmemelidir.
9. Veri Modelini Tasarlayın
Artık sistemin temel entity’lerini oluşturabiliriz.
Örneğin:
Customer
Device
ServiceRequest
WorkOrder
Technician
WorkOrderAssignment
SparePart
WorkOrderPart
ServiceReport
WorkOrderStatusHistory
Ancak entity isimlerini yazmak veri modeli tasarlamak değildir.
Asıl önemli bölüm ilişkileri ve iş kurallarını modellemektir.
İş Emri Durumu Nasıl Tutulmalı?
Basit bir yaklaşım şöyle olabilir:
WorkOrder
id
status
createdAt
updatedAt
Örneğin:
status = COMPLETED
Sistemin mevcut durumunu biliyoruz.
Ancak yönetici bir soru soruyor:
Bu iş neden gecikti?
Elimizde cevap yok.
Çünkü iş emrinin sadece son durumunu sakladık.
Şunları bilmiyoruz:
Ne zaman oluşturuldu?
Ne zaman teknisyen atandı?
Teknisyen görevi ne zaman kabul etti?
Servis ne zaman başladı?
İş ne zaman tamamlandı?
Bu nedenle bir status history yapısı oluşturabiliriz.
WorkOrderStatusHistory
id
workOrderId
oldStatus
newStatus
changedBy
changedAt
Örneğin:
OPEN → ASSIGNED
09:10
ASSIGNED → ACCEPTED
09:18
ACCEPTED → IN_PROGRESS
10:02
IN_PROGRESS → COMPLETED
11:37
Artık birçok metriği hesaplayabiliriz.
Atama süresi.
Teknisyen kabul süresi.
Müşteriye ulaşma süresi.
Ortalama servis süresi.
SLA ihlalleri.
Doğru veri modeli sadece bugünkü ekranı değil, gelecekte sorulabilecek soruları da belirli ölçüde düşünmelidir.
10. Geçmiş Verileri Koruyun: Snapshot Mantığı
Teknisyen bir servis işleminde yedek parça kullandı.
Örneğin:
Motor X
Fiyat: 500 TL
İş emri tamamlandı.
Üç ay sonra Motor X fiyatı güncellendi:
Motor X
Fiyat: 900 TL
Eski servis raporunu açtığımızda hangi fiyat görünmeli?
500 TL.
Ancak WorkOrderPart tablosunda sadece şu alanı sakladıysak:
sparePartId
rapor ekranı güncel ürün fiyatını okuyabilir.
Bu durumda geçmiş bir kayıt değişmiş olur.
Bu nedenle işlem anındaki değerleri snapshot olarak saklamak gerekebilir.
WorkOrderPart
id
workOrderId
sparePartId
partName
quantity
unitPrice
Parça kaydındaki güncel fiyat 900 TL olabilir.
Ancak eski servis kaydında:
unitPrice = 500
değeri korunur.
Benzer snapshot mantığı farklı sistemlerde de kullanılabilir.
Siparişlerde ürün fiyatı.
Faturalarda müşteri unvanı.
Rezervasyonlarda paket fiyatı.
Geçmiş işlemler her zaman güncel ana tablolardan yeniden oluşturulmamalıdır.
11. Durum Geçişlerini Kontrol Edin
Bir iş emrinin şu durumlara sahip olduğunu düşünelim:
OPEN
ASSIGNED
ACCEPTED
IN_PROGRESS
COMPLETED
CANCELLED
Frontend’e bir dropdown ekleyip kullanıcının istediği durumu seçmesine izin verebiliriz.
Teknik olarak çok kolaydır.
Ancak şu geçiş mümkün müdür?
OPEN → COMPLETED
Peki:
CANCELLED → IN_PROGRESS
Veya:
COMPLETED → ACCEPTED
Muhtemelen hayır.
Bu nedenle durum değişiklikleri açık kurallarla yönetilmelidir.
Örneğin:
OPEN
↓
ASSIGNED
↓
ACCEPTED
↓
IN_PROGRESS
↓
COMPLETED
Ayrıca bazı durumlarda:
OPEN → CANCELLED
ASSIGNED → CANCELLED
geçişlerine izin verilebilir.
Bu yapı bir state machine mantığıyla ele alınabilir.
Örneğin:
const allowedTransitions = {
OPEN: ["ASSIGNED", "CANCELLED"],
ASSIGNED: ["ACCEPTED", "CANCELLED"],
ACCEPTED: ["IN_PROGRESS", "CANCELLED"],
IN_PROGRESS: ["COMPLETED"],
COMPLETED: [],
CANCELLED: [],
};
Asıl kontrol backend tarafında yapılmalıdır.
Frontend kullanıcı deneyimini iyileştirmek için geçersiz seçenekleri gizleyebilir.
Ancak güvenlik ve iş kuralı kontrolü frontend’e bırakılamaz.
12. API Sözleşmelerini Belirleyin
Frontend ve backend ekipleri geliştirmeye başlamadan önce temel API sözleşmelerinin belirlenmesi ciddi zaman kazandırır.
Örneğin teknisyene atanmış işleri listeleyen endpoint:
GET /technicians/me/work-orders
Response:
{
"data": [
{
"id": "wo_123",
"status": "ASSIGNED",
"customer": {
"id": "cus_45",
"name": "ABC Üretim"
},
"device": {
"id": "dev_89",
"name": "Industrial Compressor"
},
"scheduledAt": "2026-07-14T09:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 8
}
}
Hata formatı da standart olmalıdır.
Örneğin:
{
"error": {
"code": "INVALID_WORK_ORDER_TRANSITION",
"message": "Work order cannot transition from COMPLETED to IN_PROGRESS"
}
}
Bir projede hata formatları standart değilse şu tablo ortaya çıkar:
Bir endpoint:
{
"message": "Not found"
}
Diğeri:
{
"error": "Not found"
}
Başka biri:
{
"errorMessage": "Not found"
}
Bir başkası HTTP 200 dönüp şöyle cevap verir:
{
"success": false
}
Frontend geliştiricisi API entegrasyonu yapmak yerine dijital arkeoloji çalışmasına başlar.
API sözleşmeleri mümkün olduğunca erken standartlaştırılmalıdır.
OpenAPI veya benzeri araçlar bu süreçte kullanılabilir.
13. Projeyi Modüllere Ayırın
Klasik bir backend klasör yapısı şöyle olabilir:
controllers/
services/
repositories/
models/
Küçük projelerde bu yapı oldukça kullanışlı olabilir.
Ancak proje büyüdükçe şu duruma gelebilir:
services/
CustomerService
DeviceService
WorkOrderService
ReportService
TechnicianService
InventoryService
NotificationService
...
Bir süre sonra services klasöründe onlarca dosya olur.
Alternatif olarak sistemi feature veya domain bazlı ayırabiliriz.
modules/
customer/
customer.controller
customer.service
customer.repository
work-order/
work-order.controller
work-order.service
work-order.repository
technician/
technician.controller
technician.service
technician.repository
Artık bir geliştirici iş emri özelliğinde çalışacaksa ilgili kodları büyük ölçüde aynı modül içerisinde bulabilir.
Kodun keşfedilebilirliği ciddi şekilde artar.
Burada tek bir doğru klasör yapısı yoktur.
Önemli olan proje büyüdüğünde yapının geliştiricinin önüne engel çıkarmamasıdır.
14. Geliştirme Planını Hazırlayın
Artık geliştirmeye başlayabiliriz.
Ancak bütün sistemi aynı anda geliştirmek yerine mantıklı bir sıra oluşturmalıyız.
Örneğin:
Sprint 1
Authentication
User management
Role ve permission yapısı
Sprint 2
Customer management
Device management
Sprint 3
Service request
Work order creation
Sprint 4
Technician assignment
Work order status management
Sprint 5
Spare part usage
Service report
Sprint 6
Dashboard
Basic reports
Ancak burada task boyutu da önemlidir.
Şu iyi bir görev değildir:
İş emri sistemi yapılacak.
Bu görev çok büyüktür.
Bunun yerine:
WorkOrder entity oluştur.
WorkOrder migration hazırla.
Create work order endpoint geliştir.
Create work order validation ekle.
Work order repository testlerini yaz.
Get work order detail endpoint geliştir.
Status transition servisini oluştur.
Invalid transition testlerini ekle.
Küçük görevler ilerlemenin takip edilmesini kolaylaştırır.
Ayrıca code review süreçlerini daha yönetilebilir hale getirir.
15. Kod Standartlarını Baştan Belirleyin
Kod standardı proje 50.000 satıra ulaştıktan sonra konuşulmamalıdır.
En başta bazı temel kararlar alınabilir.
Örneğin:
Naming convention
Folder structure
Error handling yaklaşımı
Logging standardı
Validation yaklaşımı
Branch stratejisi
Commit mesajı formatı
Pull request süreci
Commit mesajlarında Conventional Commits kullanılabilir.
Örneğin:
feat: add work order assignment
fix: prevent invalid status transition
refactor: extract work order validator
test: add technician assignment tests
Amaç geliştiricileri kurallarla boğmak değildir.
Amaç ortak bir dil oluşturmaktır.
Bir projede herkes kendi stilinde hata yönetimi, klasör yapısı ve isimlendirme kullanıyorsa proje kısa sürede tutarsız hale gelir.
16. Test Stratejisini Oluşturun
“Test yazacağız.”
Bu cümle tek başına test stratejisi değildir.
Hangi katmanın nasıl test edileceği belirlenmelidir.
Unit Test
İş kuralları test edilebilir.
Örneğin:
Geçersiz status transition.
Yetkisiz teknisyen işlemi.
SLA hesaplama.
Fiyat hesaplama.
Validation kuralları.
Örneğin:
COMPLETED → IN_PROGRESS
geçişinin engellendiğini unit test ile kontrol edebiliriz.
Integration Test
Database veya dış kaynaklarla etkileşime giren bölümler test edilebilir.
Örneğin:
WorkOrder repository.
Technician assignment transaction.
Service report creation.
API endpoint’leri.
End-to-End Test
Kritik kullanıcı akışları korunabilir.
Örneğin:
Operasyon personeli giriş yapar
↓
Servis talebi oluşturur
↓
İş emri oluşturur
↓
Teknisyen atar
↓
Teknisyen giriş yapar
↓
Görevi kabul eder
↓
Servisi başlatır
↓
Servis raporu oluşturur
↓
İş emrini tamamlar
Her şeyi E2E test etmek pahalı olabilir.
Ancak ürünün temel akışını hiç E2E test etmemek production ortamını test ortamına çevirebilir.
Kritik kullanıcı akışları korunmalıdır.
17. Transaction ve Veri Bütünlüğünü Düşünün
Teknisyen bir iş emrine atanıyor.
Bu işlem sırasında:
Assignment kaydı oluşturuluyor.
WorkOrder durumu
ASSIGNEDyapılıyor.StatusHistory kaydı oluşturuluyor.
İlk işlem başarılı oldu.
İkinci işlem başarılı oldu.
Üçüncü işlem hata verdi.
Şimdi elimizde tutarsız bir veri oluşabilir.
Bu nedenle ilişkili işlemlerde transaction kullanımı değerlendirilmelidir.
Örneğin:
BEGIN TRANSACTION
Create WorkOrderAssignment
Update WorkOrder Status
Create StatusHistory
COMMIT
Bir hata oluşursa:
ROLLBACK
Ancak her işleme transaction eklemek de doğru değildir.
Transaction kapsamı mümkün olduğunca küçük tutulmalıdır.
Uzun süren network isteklerini database transaction içerisinde çalıştırmak ciddi sorunlar oluşturabilir.
Örneğin:
BEGIN TRANSACTION
Database update
External SMS API call
External Email API call
External ERP API call
COMMIT
SMS servisi 20 saniye cevap vermezse database transaction 20 saniye açık kalabilir.
Bu nedenle database işlemleri ve dış sistem entegrasyonları dikkatli şekilde ayrılmalıdır.
18. Asenkron İşleri Ayırın
İş emri oluşturulduktan sonra teknisyene bildirim göndermek istediğimizi düşünelim.
API içerisinde şu işlemleri yapabiliriz:
Create Work Order
Assign Technician
Send Push Notification
Send Email
Send SMS
Return Response
Push servisi yavaşsa kullanıcı bekler.
SMS sağlayıcısı hata verirse iş emri oluşturma işlemi başarısız mı sayılmalı?
Muhtemelen hayır.
İş emri oluşturmak ana işlemdir.
Bildirim ise yan etkidir.
Bu nedenle bazı işlemler background job veya message queue üzerinden çalıştırılabilir.
Örneğin:
Create Work Order
↓
Save Database
↓
Publish WorkOrderAssigned Event
↓
Return Response
Background worker:
WorkOrderAssigned
↓
Send Push Notification
↓
Send Email
Bu yaklaşım ana işlemin dış sistemlere bağımlılığını azaltabilir.
Ancak queue kullanmak otomatik olarak sistemi daha iyi yapmaz.
Queue beraberinde şu problemleri getirir:
Duplicate message
Retry
Poison message
Dead Letter Queue
Idempotency
Monitoring
Bir teknolojiyi yalnızca popüler olduğu için sisteme eklemek yerine gerçekten çözdüğü problemi anlamak gerekir.
19. Development, Staging ve Production Ortamlarını Ayırın
Lokal geliştirme sırasında production database’e bağlanmak yapılmaması gereken en temel hatalardan biridir.
En azından şu ortamlar bulunabilir:
Development
Staging
Production
Her ortamın:
Database’i,
API adresi,
Environment variable’ları,
Secret değerleri,
External service configuration’ları
ayrı olmalıdır.
Örneğin SMS servisinde development ortamında gerçek SMS gönderilmesi gerekmeyebilir.
Staging ortamında test sağlayıcısı kullanılabilir.
Production ortamında gerçek provider aktif olabilir.
Secret değerleri repository içerisinde tutulmamalıdır.
Şu dosyayı Git’e göndermek oldukça kolaydır:
.env
İçerisinde:
DATABASE_PASSWORD
JWT_SECRET
AWS_SECRET_ACCESS_KEY
bulunduğunu üç ay sonra fark etmek ise pek eğlenceli değildir.
Secret management geliştirme sürecinin bir parçası olmalıdır.
20. CI/CD Sürecini Kurun
Deployment işlemi tek bir geliştiricinin bilgisayarına bağlı olmamalıdır.
Ahmet deploy ediyor.
Ahmet izinliyse?
Bekliyoruz.
Production sürecinin belirli bir kişinin laptopuna bağlı olması ciddi bir operasyonel risktir.
Basit bir CI pipeline şu adımlardan oluşabilir:
Install Dependencies
↓
Lint
↓
Type Check
↓
Unit Tests
↓
Integration Tests
↓
Build
Tüm kontroller başarılıysa deployment aşamasına geçilebilir.
CI/CD’nin amacı sadece hızlı deploy değildir.
Asıl amaç hatalı kodun production’a ulaşmasını zorlaştırmaktır.
Pull request açıldığında testler başarısızsa merge engellenebilir.
Build alınamıyorsa deploy başlamaz.
Bu basit kontroller production problemlerinin bir bölümünü daha kod merge edilmeden yakalayabilir.
21. Logging ve Observability Yapısını Kurun
Production ortamında hata oluştuğunda şu cümle pek yardımcı olmaz:
Bende çalışıyor.
Şu sorulara cevap verebilmemiz gerekir:
Hata ne zaman başladı?
Hangi kullanıcı etkilendi?
Hangi endpoint hata verdi?
Request ne kadar sürdü?
Database sorgusu ne kadar sürdü?
Hangi dış servis cevap vermedi?
Kaç kullanıcı aynı problemi yaşadı?
Bunun için en azından şu yapılar değerlendirilebilir:
Structured logging
Error tracking
Metrics
Health checks
Şu log mesajını düşünelim:
Error occurred
Neredeyse hiçbir şey anlatmıyor.
Bunun yerine:
Work order completion failed
workOrderId=wo_123
technicianId=tech_45
currentStatus=ASSIGNED
requestedStatus=COMPLETED
errorCode=INVALID_STATUS_TRANSITION
çok daha fazla bağlam sağlar.
Ancak loglara:
Password
Access token
Refresh token
Hassas müşteri verileri
yazılmamalıdır.
Observability sonradan eklenen bir lüks değildir.
Production sisteminin gözleri ve kulaklarıdır.
22. Health Check Sadece “API Ayakta” Demek Değildir
Basit bir health endpoint şöyle olabilir:
GET /health
Response:
{
"status": "ok"
}
API process’i çalışıyorsa ok döner.
Ancak PostgreSQL bağlantısı kopmuş olabilir.
Redis erişilemiyor olabilir.
Object storage çalışmıyor olabilir.
API teknik olarak ayaktadır.
Fakat kullanıcı hiçbir işlem yapamıyordur.
Bu nedenle health check stratejisinde farklı kontroller değerlendirilebilir.
Liveness
Uygulama process’i çalışıyor mu?
Readiness
Uygulama gerçekten trafik kabul etmeye hazır mı?
Örneğin kritik database bağlantısı çalışıyor mu?
Bu ayrım özellikle container ve orchestration ortamlarında oldukça önemlidir.
Yeşil bir health endpoint her zaman sağlıklı bir sistem anlamına gelmez.
23. Production’a Kontrollü Çıkın
Aylarca geliştirme yaptıktan sonra sistemi bir gecede tüm müşterilere açmak ciddi risk oluşturabilir.
Bunun yerine aşamalı bir yayın stratejisi kullanılabilir.
Örneğin:
Internal Test
↓
1 Pilot Müşteri
↓
5 Müşteri
↓
20 Müşteri
↓
Tüm Müşteriler
İlk pilot müşteride şu problemi fark edebilirsiniz:
Teknisyenlerin çoğu servis alanında internet erişimi olmadan çalışıyor.
Mobil uygulamanız offline kullanım desteklemiyor.
Bu gereksinimi kullanıcı görüşmelerinde kimse söylememiş olabilir.
Çünkü bazen kullanıcılar ihtiyaçlarını anlatmaz.
Sistemi kullanırken gösterir.
Feature flag kullanımı da burada değerlidir.
Yeni servis raporu ekranını production’a deploy edebilirsiniz.
Ancak sadece pilot kullanıcılar için aktif edebilirsiniz.
Bu yaklaşım deployment ve release kavramlarını birbirinden ayırır.
24. Gerçek Kullanıcı Davranışını Ölçün
Production’a çıkmak projenin sonu değildir.
Aslında ürün geliştirme sürecinin gerçek anlamda başladığı noktadır.
Şu soruları cevaplamamız gerekir:
Kaç servis talebi oluşturuluyor?
Taleplerin yüzde kaçı iş emrine dönüşüyor?
Ortalama teknisyen atama süresi nedir?
Teknisyenler görevi ne kadar sürede kabul ediyor?
Ortalama servis süresi ne kadar?
Hangi cihaz türlerinde daha fazla arıza oluşuyor?
Kullanıcılar en çok hangi ekranda sistemi terk ediyor?
Bir özellik geliştirdiğinizi düşünün.
Yapay zekâ destekli arıza öneri ekranı.
Üç hafta geliştirdiniz.
Production’a çıktı.
Kullanım oranı yüzde 2.
Aynı dönemde teknisyen ekranına eklediğiniz basit bir:
Son kullanılan parçaları göster
butonu teknisyenlerin yüzde 80’i tarafından kullanılıyor.
Ürün geliştirme tahminle değil ölçümle ilerlemelidir.
Bazen geliştiricilerin teknik olarak en çok gurur duyduğu özellik kullanıcı için hiçbir anlam ifade etmeyebilir.
Bazen iki saatlik küçük bir iyileştirme operasyon süresini ciddi şekilde azaltabilir.
25. Feedback Döngüsü Oluşturun
Bir projenin geliştirme süreci şu şekilde olmamalıdır:
Analiz
↓
Geliştirme
↓
Production
↓
Bitti
Daha sağlıklı bir süreç şöyledir:
Problem
↓
Hipotez
↓
Geliştirme
↓
Production
↓
Ölçüm
↓
Feedback
↓
Yeni Karar
Örneğin teknisyen atama sistemini geliştirdik.
Başlangıçta operasyon personeli manuel teknisyen seçiyor.
Production verilerini incelediğimizde şunu görüyoruz:
Operasyon personeli teknisyen seçerken sürekli üç bilgiye bakıyor:
Teknisyenin konumu
Açık iş sayısı
Uzmanlık alanı
Artık elimizde gerçek veri vardır.
Bir sonraki sürümde sistem uygun teknisyenleri otomatik olarak sıralayabilir.
Daha sonra yeterli veri oluşursa otomatik atama sistemi geliştirilebilir.
Belki ileride bir optimizasyon algoritması kullanılabilir.
Yani ilk günden karmaşık bir otomatik atama motoru yazmak yerine ürün gerçek kullanım verisiyle evrilir.
Sonuç
Bir yazılım projesi geliştirmek sadece kod yazmak değildir.
Kod elbette sürecin önemli bir parçasıdır.
Ancak tek parçası değildir.
Bir projeyi fikirden production ortamına götürürken süreç kabaca şöyle ilerleyebilir:
Problem
↓
Kullanıcı
↓
Gereksinimler
↓
MVP
↓
User Flow
↓
İş Kuralları
↓
Teknik Analiz
↓
Mimari
↓
Veri Modeli
↓
API Sözleşmeleri
↓
Modüler Yapı
↓
Geliştirme Planı
↓
Kod Standartları
↓
Test Stratejisi
↓
CI/CD
↓
Observability
↓
Production
↓
Ölçüm
↓
Feedback
Elbette iki haftalık bir MVP ile bankacılık sistemi aynı süreç yoğunluğuna sahip olmamalıdır.
Her projede Kafka kullanmak gerekmez.
Her projede Kubernetes gerekmez.
Her proje için yüz sayfalık analiz dokümanı hazırlamak da gerekmez.
Ancak projenin büyüklüğünden bağımsız olarak temel düşünce değişmez:
Önce neyi ve neden geliştirdiğinizi anlayın.
Sonra nasıl geliştireceğinize karar verin.
Çünkü yanlış problemi mükemmel kodla çözmek, hâlâ yanlış problemi çözmektir.
Ve bazen bir projede yazdığınız en iyi kod, aslında hiç yazmamanız gereken koddur.
## Summary
This article explains why software projects should not start with writing code and covers problem definition, user identification, functional and non-functional requirements, MVP scoping, user flows, business rules, technical analysis, system architecture, data modeling, snapshot patterns, state transitions, API contracts, modular project structure, development planning, code standards, testing strategy, transactions and data integrity, asynchronous processing, environment separation, CI/CD, observability, health checks, controlled production rollout, measuring user behavior, and feedback loops.
İlgili İçerikler