Native Mobil Uygulamalar için Sunucu Güdümlü Arayüz
Sunucu güdümlü arayüz, sunucu tarafı kompozisyonun mobil karşılığıdır. Zor kısmı JSON render etmek değil, eski sürümlerde ayakta kalan sürümlenmiş bileşen sözleşmesidir.
Bir native uygulama yayınlarsınız, sonra her hafta bir vitrin ekranını, bir promosyon afişini veya bir ödeme adımını değiştirmeniz gerekir. Her değişiklik app-store incelemesini ve kullanıcıların güncellemesini bekler; zaten yüklü olan sürümler aylarca kullanıcıların cebinde kalır, yani bir sunucuyu yeniden dağıttığınız gibi istemciyi geri alamazsınız. Sunucu güdümlü arayüz (SDUI) buna, yeni native kod yerine bir arayüz tarifi göndererek yanıt verir: buradaki öneri, onu içerik biçimli yüzeyler için bir neşter gibi kullanmak ve sözleşmeyi eski istemciler zarifçe geri çekilecek biçimde tasarlamaktır.
Web Karşılığı ve Mobildeki Ek Kısıt#
Web’de sunucu tarafı kompozisyon birleştirilmiş HTML gönderir ve değişiklik, tarayıcı sayfayı çektiği anda canlı olur. Native mobilde bunu yapamazsınız. App-store inceleme kapısı sizinle her ikili dosya arasında durur, yani native kodu talep üzerine gönderemezsiniz; en yeni istemciyi varsayan bir sunucu değişikliği, henüz güncellememiş herkesi bozar.
SDUI, o web kalıbının mobil karşılığıdır. Render edilmiş işaretleme veya yeni native kod yerine sunucu bir bileşen ağacı (genellikle JSON olan bir yerleşim tarifi) gönderir ve halihazırda yüklü native istemci bunu bir bileşen kayıt defteri (registry) üzerinden render eder. Airbnb’nin Ghost Platform’u, arayüz ile veriyi web, iOS ve Android için tek bir paylaşılan GraphQL şeması üzerinden birlikte taşır. REI ise filtreleme ve sıralama ekranlarını tamamen backend’in döndürdüğü esnek bir JSON şemasından üretir.
Sürümlenmiş bileşen sözleşmesi, SDUI’nin production’da işe yarayıp yaramayacağını belirler. Native istemci yalnızca daha önce yayınladığı bileşenleri render edebilir ve o yayınlanmış sürümler aylarca sahada kalır.
Bir SDUI Yanıtının Biçimi#
Sunucu bir bileşen ağacı üretir. Her düğüm kayıtlı bir bileşen type’ı belirtir, props veya veri taşır ve isteğe bağlı olarak children içerir. Örnek bir taslak:
{
"screen": "home_promo",
"version": 3,
"components": [
{ "type": "Banner", "props": { "title": "Yaz indirimi", "imageUrl": "https://cdn.example.com/s.jpg" } },
{ "type": "ProductCarousel", "props": { "items": [] } },
{
"type": "RichTextCard",
"props": { "markdown": "**Yeni** yerleşim" },
"fallback": { "type": "TextCard", "props": { "text": "Yeni yerleşim" } }
}
]
}
Bu zarftaki iki şey tüm tasarımı taşır. Üst düzey version, istemcinin yükün neyi varsaydığı hakkında akıl yürütmesini sağlar. Düğüm başına fallback, daha yeni bir bileşen eksik olabilecekken sunucunun eski bir istemciye kesinlikle yayınladığı bir bileşeni vermesini sağlar.
İstemci Bileşen Kayıt Defteri#
Native istemci bir kayıt defteri tutar: bir type dizesi, bir native görünüm üreticisine eşlenir. Ağacı dolaşır, her type’ı arar ve native görünümler kurar. Kritik nokta, kayıt defterinin bilmediği bir type geldiğinde ne olacağıdır. Bu, geriye dönük uyumluluğun bel kemiğidir; dolayısıyla sonraya bırakılamaz.
İşte zorunlu bilinmeyen-bileşen geri çekilmesiyle (fallback) birlikte SwiftUI’deki kayıt defteri. Aynı biçim bir Jetpack Compose @Composable haritasına veya bir React Native bileşen haritasına da uygulanır.
struct ComponentSpec: Decodable {
let type: String
let props: [String: JSONValue]
let fallback: Box<ComponentSpec>? // özyinelemeli, isteğe bağlı
}
@MainActor
final class ComponentRegistry {
typealias Builder = (ComponentSpec) -> AnyView
private var builders: [String: Builder] = [:]
func register(_ type: String, _ builder: @escaping Builder) {
builders[type] = builder
}
/// Bir spec'i bir görünüme çözer. Bilinmeyen tipler önce beyan
/// edilmiş fallback'i dener, sonra çökmez; hiçbir şey render etmeden geçer.
func view(for spec: ComponentSpec) -> AnyView {
if let builder = builders[spec.type] {
return builder(spec)
}
if let fallback = spec.fallback?.value {
return view(for: fallback) // güvenli alternatife geri dön
}
// Ne builder ne fallback var: bu düğümü atla, ekranı ayakta tut.
return AnyView(EmptyView())
}
}
Kayıt defterinin işi, bilinmeyen bir type geldiğinde uygulamanın çökmemesini ve tüm ekranın boş kalmamasını sağlamaktır. Beyan edilmiş fallback’i render eder veya hiçbir şey render etmez ve ağacın geri kalanıyla devam eder.
Sürümlenmiş Bileşen Sözleşmesi#
İstemci yalnızca yayınladığını render edebilir ve mağaza dağıtımlı sürümler aylarca yüklü kalır; bu yüzden sözleşme, sürüm uyumsuzluğunu normal durum olarak varsaymalıdır.
Belgelenmiş iki geri çekilme stratejisi vardır ve çoğu ekip ikisini de kullanır. İstemci tarafı fallback ile kayıt defterinin varsayılan bir işleyicisi vardır: bilinmeyen bir type genel bir yer tutucu döndürür veya hiçbir şey render etmez. REI, arayüz gösterimlerini sabit kodlar; böylece istemci, backend’in henüz göndermeye hazır olmadığı veriyle ileriye dönük uyumlu kalır. Sunucu tarafı fallback ile yanıt bir alternatif gömer (bir RichTextCard, daha basit bir TextCard taşır). Sunucu, her bileşenin hangi uygulama sürümünden itibaren mevcut olduğunun haritasını tutar ve yükü buna göre uyarlar; bu yüzden istemciler her istekte framework, uygulama ve OS sürümünü bildiren bir başlık gönderir.
Sürüm uyumluluğu yüzeyi bir matristir. Bir yük sürümünü yüklü bir istemci sürümüne eşlemek üç sonuç verir ve sözleşme, her hücreyi başarısızlık durumundan uzak tutmak için vardır.
Okuyucuya verilecek kural tek cümledir: props yalnızca eklemelidir; bir alanı asla yeniden amaçlandırmayın veya kaldırmayın, bileşeni değiştirmek yerine sürümleyin ve her yeni bileşeni bir fallback ile birlikte gönderin. Apple’ın 2.5.2 yönergesi bunu bir veri-değil-mantık disiplinine dönüştürür. İstemcinin okuduğu bir arayüz tarifi göndermek sorun değildir. Uygulamanın davranış olarak değerlendirdiği bir yapılandırma göndermek ise, yönergenin izin vermediği indirilmiş yürütülebilir koda doğru kayar; tarif ile davranış arasındaki bu çizginin pratikte öznel olduğu kabul edilir.
Spec Biçimi Seçenekleri#
Ekipler, ağdan gelen yüke ne kadar güvendikleri konusunda keskin biçimde ayrışır ve seçtikleri format bunu yansıtır.
Sürümlenmiş JSON zarfı esnek ve insan tarafından okunabilir, ama zayıf tiplidir; REI’nin şeması ve DoorDash’in Facets framework’ü (bir Facet’i bire bir bir görünüme eşleyen) bu yolu izler. GraphQL, tipleme yelpazesinin diğer ucundadır: Airbnb’nin Ghost Platform’u web, iOS ve Android’i tek bir paylaşılan şemadan besler ve Apollo bunu, API’nin alan verisinden ayrı olarak ürün ve arayüz bilgisi döndürmesi şeklinde belgeler. Protobuf veya tipli bir IDL ise bu esnekliğin bir kısmından vazgeçip iki uç için güçlü tipleme ve kod üretimi kazandırır; MobileNativeFoundation katkıcıları, düğme ve yerleşim gibi ilkelleri protobuf’ta tanımlayıp her iki tarafta native ve web render ediciler kullanmayı anlatır.
Sınır durumu yalnızca veri değil, mantık göndermektir. Cash App’in Redwood’u, Treehouse ve Zipline ile birlikte, bildirimsel bir ağaç yerine istemcide yürütülen Kotlin/JS gönderir (WebAssembly ise belirttikleri gelecekteki yöndür). Bu, bildirimsel SDUI’ye göre indirilmiş yürütülebilir kod için 2.5.2 yönergesi çizgisine çok daha yakın oturur. Güçlü bir yaklaşımdır, farklı bir risk duruşu taşır; onu aşağıdaki varsayılan tavsiyenin ötesinde bir sınır durum olarak ele alın.
Tarihsel bağlam için, Spotify’ın HubFramework’ü iOS’ta geniş ölçekte çalışan en erken bileşen güdümlü arayüz sistemlerindendi; artık arşivlenmiş ve kullanımdan kaldırılmış durumda.
Önerilen Varsayılan#
SDUI’yi dinamik, içerik biçimli yüzeyler için bir neşter gibi kullanın: vitrin, promosyon, formlar, özellik bayraklı ve A/B testli yerleşimler. Etkileşim yoğun, jest ve animasyon zengini, gecikmeye duyarlı ve çevrimdışı öncelikli ekranları native tutun. Sözleşme için, açık bir type kayıt defteri ve zorunlu bir bilinmeyen-bileşen fallback kuralı olan sürümlenmiş bir JSON zarfını varsayılan yapın; tam tipli protobuf IDL’i aşağıdaki geçersiz kılma durumlarına saklayın.
Eski istemcilerde zarif şekilde geri çekilmek işin kendisidir ve gevşek-ama-sürümlü JSON, görmediği bir alanı çözmeyi reddeden katı bir şemadan daha zarif geri çekilir. Trade-off gerçektir: derleme zamanı güvenliğinden vazgeçer ve bileşen başına bir fallback sözleşmesi disiplinini üstlenirsiniz.
Varsayılanı Ne Zaman Geçersiz Kılmalı#
Protobuf veya tipli IDL geçersiz kılması; iki ucun yeterince sık birlikte yayınlandığı, dolayısıyla sürüm uyumsuzluğunun sınırlı kaldığı ve kod üretiminin değdiği performansa duyarlı, yüksek hacimli, sıkı bağlı dahili yüzeyler için geçerlidir. WebView geçersiz kılması ise, native his yerine gerçekten web içeriği yeniden kullanımına ihtiyaç duyduğunuzda geçerlidir.
Bu son durum net bir ayrımı hak eder, çünkü ekipler aynı dinamizm sorununu çözmek için sıkça hem SDUI’ye hem WebView’lara uzanır. SDUI bir sunucu tarifinden native görünümler render eder; render etmenin sahibi istemcidir ve sonuç native hisseder. Bir WebView mikro-frontend’i ise web içeriğini bir native kabuğa gömer; tam web yeniden kullanımı ve anında değişim elde edersiniz ama native olmayan his, köprü karmaşıklığı ve ayrı bir çalışma zamanı bedelini ödersiniz. Yanıt gerçekten bir web ekibinin sahip olduğu web biçimli içerikse, geçersiz kılma budur ve WebView serisi onun mekaniğini kapsar.
Başarısızlık Biçimleri ve İzlenecek Sinyaller#
En sık görülen hata, önceki bölümdeki fallback kuralını atlamaktır: eski bir istemci, geri çekilecek hiçbir şeyi olmayan bilinmeyen bir type ile karşılaşır ve ekran içerik yerine boş render edilir. Kayıt defteri kodu bunu zaten mekanik olarak çözer; disiplin, kuralı riskli görünmeyenler dahil her yeni bileşene uygulamaktan ibarettir.
Yakın bir ikincisi kod incelemesinde ortaya çıkar: bir prop eklenmek yerine yeniden amaçlandırılır veya kaldırılır, orijinal alanı hâlâ okuyan eski istemciler yanlış render eder. Aynı kaymanın daha sessiz bir biçimi, istemci yeteneklerini uygulama sürüm numarasından tahmin etmekten gelir; bunun yerine istemcinin her istekte kayıt defteri veya yetenek sürümünü bildirmesine izin verin ve yükü bildirilenlere göre uyarlayın.
İki hata daha sözleşmenin dışında, kapsam kararlarında yaşar. Jest yoğun veya gecikmeye duyarlı ekranlar SDUI üzerinden geçirildiğinde kasan, ağa bağımlı bir hisse dönüşür; bunları native tutun. Ve koşullar, ifadeler ve stil seçenekleri biriktirmeye devam eden bir tarif, JSON’a esneklik eklemeyi bırakıp bir sonraki isteği bir WebView’a yönlendirme zamanının geldiğine işarettir.
SDUI’yi işletirken sözleşme sağlığını izleyin. İzlemeye değer sinyaller şunlardır: fallback isabet oranı (yüksek olması sözleşme kayması demektir), mevcut yükü render edebilen yüklü istemci sürümlerinin payı, uygulama sürümüne göre boş ekran veya render hatası oranı ve SDUI ekranlarının native ekranlara oranı. Dinamik bir yüzey için değişim süresi, kalıbın satın alındığı ölçüttür: bir içerik değişikliğinin yayın olmadan canlıya çıkması ne kadar sürdüğüdür.
Kapanış#
Varsayılan, içerik biçimli ve sık değişen, zengin etkileşime, düşük gecikmeye veya çevrimdışı desteğe ihtiyaç duymayan yüzeylerde geçerlidir. Geçersiz kılmayı yalnızca yukarıdaki durumlarda kullanın: kod üretimini haklı çıkaracak kadar sıkı birlikte yayınlanan uçlar için protobuf, içeriğin gerçekten bir web ekibine ait olduğu durumlar için WebView.
Kaynaklar#
- A Deep Dive into Airbnb’s Server-Driven UI System (Ghost Platform) (yeni sekmede açılır) - Kanonik kaynak: web/iOS/Android genelinde paylaşılan bir GraphQL şeması, arayüz ve veri birlikte taşınır
- The Journey to Server Driven UI At Lyft Bikes and Scooters (yeni sekmede açılır) - Bir ekibin SDUI’yi neden benimsediği: iş karmaşıklığı, yayın hızı ve kadro esnekliği
- Improving Development Velocity with Generic, Server-Driven UI Components (DoorDash, Facets) (yeni sekmede açılır) - Bir Facet’in bire bir bir görünüme eşlendiği bir yerleşim motoru artı bileşen kütüphanesi
- Server Driven UI: Prepared for the Unknown (REI Co-op Engineering) (yeni sekmede açılır) - İleriye dönük uyumluluk için güçlü kaynak: esnek bir JSON şeması ve backend’in göndermeye hazır olmadığı veriyle ileriye dönük uyumlu istemci
- Spotify HubFramework (kullanımdan kaldırıldı) (yeni sekmede açılır) - Ölçekte erken bir bileşen güdümlü iOS arayüz framework’ü, artık arşivli; yalnızca tarihsel öncül olarak anılır
- Server-driven UI strategies (MobileNativeFoundation Discussion #47) (yeni sekmede açılır) - Protobuf-ilkelleri yaklaşımı ve SDUI’nin çevrimdışı ile animasyon sınırlamaları
- Native UI and multiplatform Compose with Redwood (Cash App Code Blog) (yeni sekmede açılır) - Zipline ve Kotlin/JS artı WebAssembly kullanan “yalnızca veri değil, mantık gönder” sınır durumu
- How to Safely Release Server-Driven UI Updates at Scale (Digia) (yeni sekmede açılır) - İstemci ve sunucu fallback stratejileri, sürüm bildiren başlıklar ve bileşen-uygunluk haritalama
- Server-Driven UI Basics (Apollo GraphQL Docs) (yeni sekmede açılır) - Sözleşmenin GraphQL-şeması çerçevesi: alan verisi değil, arayüz ve ürün bilgisi döndür
- App Review Guidelines (Apple Developer, Guideline 2.5.2) (yeni sekmede açılır) - SDUI’yi hem mümkün kılan hem sınırlayan kısıt: veri tarifleri serbest, indirilmiş yürütülebilir kod yasak
- Fixing Section 2.5.2 (Saagar Jha) (yeni sekmede açılır) - Veri-mantık çizgisinin pratikte neden öznel olduğu
- Shipping Mobile When You Can’t Roll Back the Client - Bu kalıbın yanıtladığı motive edici kısıt
- Server-Side Micro-Frontend Composition - Bu fikrin web karşılığı
- Mobile Micro-Frontends with React Native and Expo WebViews - Karşılaştırılan WebView alternatifi
İlgili yazılar
Mobil binary geri alınamaz ve eski sürümler kalıcıdır; güvenlik ve hız sunucuya kayar: BFF, tüketici güdümlü sözleşmeler ve geriye dönük uyumlu sürümleme.
mobile · api-design · testing +1
Async backend'le çalışan tasarımcılar için pragmatik rehber: üç etkileşim şekli, hangisi ne zaman, ve karşı durmanız gereken dört anti-pattern.
event-driven · state-management · design-patterns +2
Headless CMS çözümlerinin (Strapi, Contentful, Kontent, Storyblok) pratik karşılaştırması: Cloudinary ile görsel yönetimi ve framework entegrasyonu.
typescript · nextjs · react-native +3
React Native ve WebView'lar ile mobil micro frontend'ler oluşturma. Çok kanallı uygulamalar için production stratejileri ve performans optimizasyonu.
expo · performance · re-pack +4
React Native ve Expo WebView'lar ile mobil micro frontend mimarisi oluşturma. Gerçek dünya örnekleri ve en iyi uygulamalar.
expo · mobile · re-pack +2