Loading, Empty, Error, Offline ve Conflict: Görünmeyen Ürün Mimarisi
Happy path ekranları ürünün yalnızca bir kısmıdır. Güvenilir ürün, veri gelmediğinde, mutation başarısız olduğunda, cihaz offline olduğunda ve iki state çatıştığında ne yapacağını önceden bilir.
Demo tasarlamak kolaydır.
Data yüklenir. Kartlar görünür. Kullanıcı tıklar. İşlem başarılı olur.
Gerçek ürün bu kadar nazik davranmaz.
Network yavaşlar. Session expire olur. Liste boş gelir. Mutation kabul edilmez. İki cihaz aynı veriyi değiştirir. Tab uykuya girer. App background’dan döner. Local state cloud state’den eski kalır.
İşte ürün mimarisinin büyük kısmı bu anlarda görünür.
Happy path sadece bir state’tir
Bir ekranı “tamamlandı” saymak için yalnız normal hâlini kontrol etmek bana yeterli gelmiyor.
En azından şu durumların davranışı düşünülmeli:
Loading Kullanıcı neyin beklendiğini anlıyor mu?
Empty Gerçekten veri yok mu, filtre mi sonuç vermedi, permission mı eksik?
Error Ne başarısız oldu? Kullanıcı ne yapabilir?
Offline Okuma devam edebilir mi? Yazma kuyruğa mı alınır? Bloke mi edilir?
Conflict İki farklı gerçeklik varsa sessizce hangisi kazanıyor?
Bu state’ler sonradan component’e eklenen mikrocopy değildir.
Domain ve persistence kararları gerektirir.
Empty state tek bir durum değildir
“Henüz not yok.”
Basit görünüyor.
Ama şu senaryolar farklıdır:
- Kullanıcı gerçekten hiç not oluşturmamış.
- Seçili klasörde not yok.
- Arama sonucu yok.
- Sync tamamlanmadı.
- Permission nedeniyle veri görünmüyor.
- Query hata verdi ama UI boş listeye düştü.
Hepsine aynı empty state’i gösterirseniz sistem teknik olarak render eder ama yanlış bilgi verir.
Bu nedenle empty state’in kaynağı domain seviyesinde anlaşılmalı.
Offline bir tasarım tercihi değildir
Offline desteği bazen checkbox gibi konuşuluyor.
“PWA olduğuna göre offline da olsun.”
Aslında şu kararları açar:
- Hangi veriler local okunabilir?
- Kullanıcı offline yazabilir mi?
- Mutation nasıl kalıcı tutulur?
- App kapanırsa pending write kaybolur mu?
- Online olunca hangi sırayla flush edilir?
- Server revision değişmişse ne olur?
- Kullanıcı conflict’i nasıl görür?
Kurmaca gibi yazma ürününde bu sorular kritik.
Yazarın sahnesini “offline destekliyoruz” diyerek localStorage’a atmak yeterli değil. Recovery’nin hangi revision’a ait olduğu ve cloud’a nasıl geri döneceği bilinmeli.
Error mesajı değil, recovery path
“Bir hata oluştu.”
Bu cümle bilgi vermiyor.
İyi error state üç şeyi netleştirir:
1. Ne başarısız oldu? 2. Kullanıcının yaptığı işlem kayboldu mu? 3. Bir sonraki güvenli hareket ne?
Örneğin kayıt başarısız olduysa:
- local draft duruyor mu,
- retry yapılabilir mi,
- duplicate mutation riski var mı,
- navigation bloke edilmeli mi?
Bunlar toast tasarımından önce çözülmeli.
Conflict en dürüst state’tir
Çok cihazlı ürünlerde “last write wins” bazen makul bir karardır.
Ama kritik içerikte sessiz overwrite kabul edilemez olabilir.
Conflict state aslında sistemin kullanıcıya şunu söylemesidir:
“İki geçerli değişiklik var ve hangisinin korunacağına otomatik karar veremiyorum.”
Bu başarısızlık değildir.
Belirsizliği görünür kılmaktır.
Bazen iyi sistem, her şeyi otomatik çözmek yerine doğru yerde karar istemelidir.
State matrisi acceptance kriterine dönüşmeli
Bir feature implement edilmeden önce küçük bir state matrisi çıkarılabilir.
- State
- Loading
- Kullanıcı ne görür?
- Skeleton / context
- Yazma yapılabilir mi?
- Hayır
- Recovery
- Bekle
- State
- Empty
- Kullanıcı ne görür?
- Açıklama + primary action
- Yazma yapılabilir mi?
- Evet
- Recovery
- Oluştur
- State
- Error
- Kullanıcı ne görür?
- Hatanın kapsamı
- Yazma yapılabilir mi?
- Duruma göre
- Recovery
- Retry / geri dön
- State
- Offline
- Kullanıcı ne görür?
- Offline göstergesi
- Yazma yapılabilir mi?
- Domain’e göre
- Recovery
- Queue / local draft
- State
- Conflict
- Kullanıcı ne görür?
- İki revision
- Yazma yapılabilir mi?
- Kontrollü
- Recovery
- Resolve
| State | Kullanıcı ne görür? | Yazma yapılabilir mi? | Recovery |
|---|---|---|---|
| Loading | Skeleton / context | Hayır | Bekle |
| Empty | Açıklama + primary action | Evet | Oluştur |
| Error | Hatanın kapsamı | Duruma göre | Retry / geri dön |
| Offline | Offline göstergesi | Domain’e göre | Queue / local draft |
| Conflict | İki revision | Kontrollü | Resolve |
Bu tablo designer için değil, build contract içindir.
Çünkü frontend, backend ve persistence aynı davranış üzerinde anlaşmalıdır.
Sonuç
Ürün güveni kullanıcı her şey yolundayken oluşmaz.
Bir şey ters gittiğinde oluşur.
Loading, empty, error, offline ve conflict state’lerini “edge case” olarak görürseniz production’daki gerçek kullanımın önemli kısmını edge’e itmiş olursunuz.
Ben bunları first-class product state olarak ele alıyorum.
Çünkü çalışan ürün, sadece başarı ekranı gösteren ürün değildir.
Yanlış giden şeyi de doğru şekilde taşıyabilen üründür.
Architecture in the Wild
Mimariyi diyagram olarak değil; canlı ürünlerde state, bilgi, interaction ve product boundary kararlarının çalışan sonucu olarak inceleyen saha dizisi.