2.000'den Fazla Çalışan İçin React Native ile Kurumsal Mobil Uygulama Geliştirmek
Yönetim, 2.000'den fazla çalışanın her gün kullanacağı bir mobil uygulama istediğinde, bunun tipik bir uygulama mağazası projesi olmadığını biliyordum. Kurumsal mobil uygulamaların kendine özgü bir dizi kısıtı vardır: kurumsal kimlik doğrulamayla, katı güvenlik politikalarıyla, eski backend entegrasyonlarıyla ve kullanıcılarla uğraşırsınız — kullanıcılar hatalara tahammül etmez, çünkü bu silebilecekleri bir sosyal medya uygulaması değil, iş araçlarıdır.
İşte DigiPort'u — React Native 0.82.1 ve React 19 üzerinde çalışan, kuruluş genelinde 500'den fazla günlük aktif kullanıcıya hizmet veren bir React Native uygulamasını — bu şekilde geliştirdim.
Mimari Kararlar
İlk karar, native geliştirme yerine React Native oldu. Küçük bir ekibimiz, sıkı teslim tarihlerimiz vardı ve hem iOS hem de Android'de yayına çıkmamız gerekiyordu. Ekip web uygulamamızdan (DigiFlow) React'i zaten biliyordu, dolayısıyla React Native doğal bir tercihti. React Native 0.82.1 ve varsayılan olarak etkinleştirilen Yeni Mimari (New Architecture) sayesinde performans artık bir endişe kaynağı değildi.
Uygulama yapısı bilinçli olarak düz ve modüler:
src/
screens/ # 38 ekran
components/ # 98 yeniden kullanılabilir bileşen
services/ # API, auth, bildirimler
stores/ # Zustand store'ları
hooks/ # Özel hook'lar
navigation/ # React Navigation stack'leri
utils/ # Yardımcılar, sabitler, tipler
Kimlik Doğrulama: MSAL ve Token Manager
MSAL üzerinden Azure AD kimlik doğrulaması en büyük teknik zorluktu. React Native için Microsoft Authentication Library çalışıyor, ancak kurumsal ortamlar size sürprizler yapar: koşullu erişim politikaları, çok faktörlü kimlik doğrulama istemleri, cihaz çevrimdışı kaldığında token yenileme hataları ve VPN'e bağımlı token endpoint'leri.
Tüm bunları zarif bir şekilde ele almak için, devre kesici (circuit breaker) desenine sahip özel bir Token Manager geliştirdim:
typescriptclass TokenManager {
private circuitBreaker: CircuitBreaker;
private refreshPromise: Promise<string> | null = null;
constructor() {
this.circuitBreaker = new CircuitBreaker({
failureThreshold: 3,
resetTimeout: 30_000, // 30 saniye
halfOpenMaxAttempts: 1,
});
}
async getAccessToken(): Promise<string> {
// Eşzamanlı token isteklerini tekilleştir
if (this.refreshPromise) return this.refreshPromise;
return this.circuitBreaker.execute(async () => {
const account = await this.getAccount();
try {
// Önce sessiz token edinimi
this.refreshPromise = msalInstance
.acquireTokenSilent({
scopes: ['api://digiflow/.default'],
account,
})
.then(result => result.accessToken);
return await this.refreshPromise;
} catch (error) {
if (error instanceof InteractionRequiredAuthError) {
// Etkileşimli oturum açmaya yönlendir
return this.acquireTokenInteractive();
}
throw error;
} finally {
this.refreshPromise = null;
}
});
}
}
Devre kesici, Azure AD'de sorun yaşandığında uygulamanın token endpoint'ini sürekli zorlamasını engeller. Üst üste 3 başarısızlıktan sonra devreyi açar ve kullanıcıya sonsuza kadar dönmek yerine anlamlı bir hata gösterir. 30 saniye sonra tek bir yeniden deneme girişimine izin verir. Bu desen iki Azure AD kesintisi sırasında bizi kurtardı — uygulama çökmek yerine zarif bir şekilde performans düşürdü.
Zustand 5 ile State Yönetimi
State yönetimi için Redux veya MobX yerine Zustand 5.0.8'i seçtim. Bir mobil uygulamada sadelik kazanır. Zustand store'ları küçük, odaklı ve sıfır boilerplate'e sahiptir:
typescriptinterface NotificationStore {
unreadCount: number;
notifications: Notification[];
isConnected: boolean;
addNotification: (notification: Notification) => void;
markAsRead: (id: string) => void;
setConnectionStatus: (status: boolean) => void;
}
export const useNotificationStore = create<NotificationStore>()(
persist(
(set, get) => ({
unreadCount: 0,
notifications: [],
isConnected: false,
addNotification: (notification) =>
set((state) => ({
notifications: [notification, ...state.notifications].slice(0, 100),
unreadCount: state.unreadCount + 1,
})),
markAsRead: (id) =>
set((state) => ({
notifications: state.notifications.map((n) =>
n.id === id ? { ...n, read: true } : n
),
unreadCount: Math.max(0, state.unreadCount - 1),
})),
setConnectionStatus: (status) => set({ isConnected: status }),
}),
{
name: 'notification-storage',
storage: createJSONStorage(() => AsyncStorage),
}
)
);
AsyncStorage ile persist middleware'i, bildirimlerin uygulama yeniden başlatmalarında hayatta kalması demektir. Kullanıcılar uygulamayı açar ve bir ağ çağrısı beklemeden okunmamış sayılarını anında görürler.
Gerçek Zamanlı Güncellemeler İçin SignalR
Doküman iş akışı onaylarının gerçek zamanlı olması gerekir. Bir yönetici bir dokümanı onayladığında, talep edenin bunu anında bilmesi gerekir. Otomatik yeniden bağlanma ve üstel geri çekilme (exponential backoff) ile SignalR'ı entegre ettim:
typescriptconst connection = new HubConnectionBuilder()
.withUrl(`${API_BASE}/hubs/workflow`, {
accessTokenFactory: () => tokenManager.getAccessToken(),
})
.withAutomaticReconnect({
nextRetryDelayInMilliseconds: (context) => {
// Üstel geri çekilme: 0s, 2s, 4s, 8s, 16s, maksimum 30s
const delay = Math.min(
Math.pow(2, context.previousRetryCount) * 1000,
30_000
);
return delay;
},
})
.build();
SignalR bağlantısı doğrudan Zustand bildirim store'una bağlanır. Bir mesaj geldiğinde store'u günceller; store ise abone olan her bileşeni reaktif olarak günceller — rozet sayıları, bildirim listeleri ve iş akışı durum göstergeleri hepsi aynı anda güncellenir.
38 Ekran, 98 Bileşen
Uygulama geniş bir alanı kapsıyor: doküman görüntüleme, iş akışı onayları, görev yönetimi, duyurular, dizin araması ve daha fazlası. 38 ekran ve 98 bileşenle, kodun düzenlenmesi önem kazanıyor.
Her ekran aynı deseni izler: bir container bileşeni veri çekme ve durum yönetimini üstlenir, bir presentation bileşeni de render işini yapar. Özel hook'lar paylaşılan mantığı dışarı çıkarır:
typescriptfunction useWorkflowActions(documentId: string) {
const queryClient = useQueryClient();
const approveMutation = useMutation({
mutationFn: (data: ApprovalData) =>
workflowService.approve(documentId, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['document', documentId] });
queryClient.invalidateQueries({ queryKey: ['pending-approvals'] });
},
});
const rejectMutation = useMutation({
mutationFn: (data: RejectionData) =>
workflowService.reject(documentId, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['document', documentId] });
queryClient.invalidateQueries({ queryKey: ['pending-approvals'] });
},
});
return { approveMutation, rejectMutation };
}
Üretimde Performans
500'den fazla günlük aktif kullanıcıyla performans yakından izleniyor. Üretimdeki 6 ayın ardından temel metrikler:
- Uygulama açılışından etkileşime kadar: ~1,8 saniye (soğuk başlatma)
- API yanıtı render etme: çoğu ekran için 200ms'nin altında
- Çökmesiz oran (crash-free): %99,7
- SignalR yeniden bağlanma başarısı: %98,5
En büyük performans kazancı, React Native 0.82.1'de Yeni Mimari'yi etkinleştirmek oldu. JSI tabanlı köprü serileştirme yükünü ortadan kaldırdı ve Fabric'in senkron render'ı, uzun doküman listelerinde kaydırmayı gözle görülür şekilde akıcılaştırdı.
Kurumsal Mobil Uygulamalar İçin Dersler
- Kimlik doğrulama altyapısına yoğun yatırım yapın. Kurumsal kimlik doğrulama, tüketici kimlik doğrulamasından 10 kat daha zordur. Token manager'ı erken ve iyi inşa edin.
- Devre kesiciler şarttır. Kurumsal ortamlarda ağ koşulları öngörülemezdir. Zarif bir şekilde performans düşürün.
- Kritik durumu kalıcılaştırın. Kullanıcılar uygulamayı sürekli kapatıp yeniden açar. Bağlamı kaybetmediklerinden emin olun.
- Gerçek cihazlarda, gerçek ağ koşullarıyla test edin. Simülatör yalan söyler.
- Haftalık yayına çıkın. Kurumsal uygulamaların hızlı yinelemesi gerekir, çünkü 2.000 kullanıcıdan gelen geri bildirim hızlı gelir.