Ne yapıyor
Dört simüle araç (ALFA-1, BRAVO-2, CHARLIE-3, DELTA-4) haritada hareket ediyor. Operatör haritaya tıklayarak çokgen bölgeler çiziyor, araçların bu bölgelere girip çıkmasını canlı olay listesinde görüyor, araçlara öncelikli görevler atıyor ve seçili aracın belirli bir tarih aralığındaki izini sorguluyor.
Eğitim amaçlı bir simülasyon; hiçbir gerçek sisteme, cihaza ya da kurumsal veriye bağlanmıyor.
Mimari
Sunucu dört katmandan oluşuyor. Domain hiçbir projeye bağımlı değil; Application veritabanına kendi tanımladığı bir arayüz üzerinden erişiyor ve Infrastructure bunu EF Core ile uyguluyor. Soyutlamaları yalnızca gerçek bir ihtiyaç olduğunda ekledim. Örneğin IPositionSource’un üç gerçek uygulaması var: simülatör, CSV kayıt dosyası ve bir NMEA plugin’i. Ayrı bir repository katmanı eklemedim, çünkü EF Core’un DbSet’i zaten bu işi görüyor.
Bir konum bildirimi sisteme girdiğinde şu yolu izliyor:
Aynı olayı iki kez üretmemek
Bu tür sistemlerde en sinsi hata, aynı bölge girişinin iki kez raporlanması. Bunu dört ayrı katmanda engelliyorum:
- Durum geçişi: Olay yalnızca “içeride / dışarıda” durumu değiştiğinde üretiliyor; üyelik
(araç, bölge)birincil anahtarlı bir tabloda tutuluyor. - İdempotent alma: Bildirimin zamanı aracın son kayıtlı zamanından yeni değilse
Duplicatesayılıyor. Zaman damgalarını PostgreSQL’in mikrosaniye hassasiyetine indirgiyorum; yoksa veritabanından okunan zaman ile tekrar gönderilen bildirim farklı görünüyordu. Bu hatayı bir test yakaladı. - Eşzamanlılık: Aynı araç üzerindeki işlemler araç başına bir kilitle sıraya giriyor. Veritabanında ek güvence olarak kısmi tekil indeksler var: araç başına tek aktif görev, aynı bölge olayının tekrarı yasak.
- İstemci: Olaylar kimliğe göre tekilleştiriliyor; yeniden bağlanınca alınan anlık görüntü, canlı akıştan gelen daha yeni bir güncellemenin üzerine yazmıyor.
Bağlantı kopunca
İstemci önce SignalR’a abone oluyor, sonra REST’ten araçları, son olayları ve bölgeleri çekiyor. Sıra önemli: tersini yapsaydım, çekme ile abonelik arasındaki boşlukta gelen güncellemeler kaybolurdu. Sunucu tamamen kapanıp açılsa bile istemci artan aralıklarla yeniden bağlanmayı deniyor ve ekranda bir uyarı bandı gösteriyor.
Coğrafi doğruluk
Harita Web Mercator’da çiziliyor ama tüm hesaplar WGS84 enlem / boylam üzerinde. Tıklanan nokta enlem / boylama çevrilip API’ye öyle gidiyor. Bölge testi sınır kutusuyla değil, NetTopologySuite ile gerçek çokgene göre yapılıyor. Bir bölge çizildiğinde içinde zaten bulunan araçlar için “giriş” olayı üretmiyorum; araç girmedi, bölge onun etrafına çizildi.
Plugin mimarisi
Konum kaynakları sabit kodlanmış değil. plugins/ klasörüne derlenmiş bir DLL bırakmak yeni bir kaynak eklemeye yetiyor; API, istemci ya da veritabanında değişiklik gerekmiyor. Kaynak, API’nin kaynak listesinde ve istemcinin açılır listesinde kendiliğinden görünüyor ve çalışma anında seçilebiliyor.
- Keşif (MEF): Plugin sınıfı
[PositionSource("Ad")]özniteliğiyle işaretleniyor. Katalog dışa aktarımlarıLazy<IPositionSource, Metadata>olarak aldığı için kaynak listesi, plugin sınıfı hiç örneklenmeden okunuyor; yalnızca seçilen kaynak oluşturuluyor. - Yalıtım: Her plugin kendi
AssemblyLoadContext’ine yükleniyor ve bağımlılıklarını kendi klasöründen çözüyor; API ile farklı sürüm bir paket kullanabiliyor. Sözleşme derlemeleri ise bilerek paylaşılıyor. Paylaşılmasaydı plugin’dekiIPositionSourceAPI’dekiyle aynı tür sayılmaz ve MEF hiçbir şeyi eşleştiremezdi; bunu bir birim testi doğruluyor. - Bağımlılık: Plugin API’nin DI kapsayıcısını görmüyor; yalnızca kendi yapılandırma bölümünü, logger’ı ve saati veren küçük bir host arayüzü alıyor.
- Hata dayanıklılığı: Bozuk bir DLL, eksik dosya ya da aynı adlı ikinci kaynak yalnızca o plugin’in atlanmasına yol açıyor; API yine açılıyor ve hata katalogda görünüyor.
İlk plugin, TCP üzerinden gelen NMEA 0183 GPS akışını ($GPRMC cümleleri) okuyor. Sağlama toplamı zorunlu; hatalı toplam, geçersiz alan (60’tan büyük dakika, 30 Şubat) ve 120 karakteri aşan satır reddediliyor. Test için GPS alıcısı yerine geçen bir Python yayıncısı yazdım; cümlelerin %5’inin sağlama toplamını bilerek bozuyor. Kesirli saniyeyi double ile ayrıştırınca 05.06 saniye 05.0599999 oluyordu; bu hatayı gidiş-dönüş testi yakaladı, decimal’a geçtim.
Teknoloji seçimleri
| Alan | Seçim | Neden |
|---|---|---|
| Çalışma zamanı | .NET 10 LTS | .NET 8’in desteği Kasım 2026’da bitiyor |
| Masaüstü | WPF + CommunityToolkit.Mvvm | Kaynak üreteçleri tekrar eden kodu kaldırıyor |
| Harita | Mapsui + OpenStreetMap | Serbest lisans, NTS geometrisini doğrudan çiziyor |
| Canlı veri | SignalR, tipli hub | Sunucudan istemciye itme, otomatik yeniden bağlanma |
| Genişletilebilirlik | MEF (System.Composition) + AssemblyLoadContext |
Plugin’leri örneklemeden keşif, bağımlılık yalıtımı |
| Veritabanı | PostgreSQL 17 + PostGIS 3.5 | GIST indeksli geometri, ST_Covers sorguları |
| Test | xUnit + Testcontainers | Entegrasyon testleri sahte değil gerçek PostGIS’e karşı |
Sınırlamalar
- Tek API örneği varsayılıyor. Birden çok örnek için dağıtık kilit ve SignalR backplane gerekir.
- Kimlik doğrulama yok; yalnızca yerel demo için.
- Nokta-çokgen testi düzlemsel. Çok büyük bölgelerde kenarlar jeodezik değil.
- API yeniden başlarsa simülatör baştan başlıyor ve araçlar ilk konumlarına “atlıyor”; bu atlama sahte bir bölge olayı üretebilir.
- Plugin’ler güvenilir kod sayılıyor: API ile aynı süreçte ve aynı yetkilerle çalışıyor;
AssemblyLoadContextbağımlılık yalıtımı sağlıyor, güvenlik yalıtımı sağlamıyor. Çalışırken kaldırılıp yeniden yüklenemiyor. - NMEA kaynağı yalnızca RMC cümlesini kullanıyor ve şimdilik yalnızca TCP destekliyor; seri port (RS-232) yok.
- WPF istemcinin durum mantığı birim testli; ekran akışlarını UI Automation betiğiyle doğruladım.


