RFC'den Production'a: Implementation Hakkında Anlatmadıkları

Güzel RFC tasarımları ile karmaşık production gerçekliği arasındaki boşluk üzerine bir Principal Engineer'ın samimi değerlendirmesi ve bildirim sistemleri örneğinden gerçek dersler

Güzelce hazırlanmış bir RFC okurken, zarif mimari diyagramlara bakıp "İşte bu, sonunda mükemmel çalışacak tasarım bu" diye düşündüğün o anı bilir misin? Sonra altı ay sonra kendini production sorunlarının içinde bulursun, timeline iki katına çıkmıştır ve o tertemiz database şeması sanki blender'dan geçmiş gibi görünür.

RFC'leri production sistemlerine dönüştürmenin gerçekliğine hoş geldin. 20 yıl boyunca parlak tasarımların organizasyonel gerçeklikle çarpışmasını izledikten sonra öğrendim ki, RFC ile production arasındaki boşluk bir bug değil – gerçek takımlarla, gerçek iş baskıları altında karmaşık sistemler inşa etmenin bir özelliği.

O 12 haftalık bildirim sistemi RFC'sinin production'ın kaosuyla buluştuğunda neler olduğunu, birden fazla implementation'dan gerçek deneyimlerle anlatayım. Spoiler: 12 hafta değil 8 ay sürdü ve final sistem orijinal tasarıma hiç benzemiyordu.

RFC Balayı Dönemi#

Her RFC iyimserlikle başlar. Aklımdaki bildirim sistemi RFC'si bir şaheserdi: temiz mimari diyagramlar, kapsamlı database şemaları, aşamalı dağıtım planları. Yaşadığımız her bildirim sorununu çözeceğini vaat ediyordu:

TypeScript
// RFC'nin güzel vaadi
interface NotificationSystemGoals {
  deliveryTime: 'in-app için <100ms, email için <5s',
  throughput: 'Saniyede 10,000+ bildirim',
  uptime: '%99.9 erişilebilirlik',
  timeline: '2 developer ile 12 hafta',
  budget: '$120,000-180,000'
}

// Gerçekte olan
interface ProductionReality {
  deliveryTime: 'İyi günlerde in-app için 2-3s, peak'lerde 30s+',
  throughput: '500/sn ile başladı, 5,000/sn\'ye ulaşmak 6 ay sürdü',
  uptime: 'İlk çeyrek %97, bir yıl sonra %99',
  timeline: '4 developer + 2 contractor ile 8 ay',
  budget: '$400,000+ ve maintenance maliyetleri hala devam ediyor'
}

RFC kusursuz görünüyordu. Her şeyi düşünmüştük: rate limiting, deduplication, preference management, hatta sessiz saatler bile. Aşamalı yaklaşım muhafazakar görünüyordu – core infrastructure için 4 hafta kesinlikle yeterli olmalıydı?

Database Şemaları Gerçeklikle Buluşunca#

RFC'nin database şeması güzelliğin ta kendisiydi. Temiz, normalize edilmiş, düzgün foreign key'ler ve constraint'ler. RFC'nin önerdiği buydu:

SQL
-- RFC'nin tertemiz şeması
CREATE TABLE notification_events (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id) ON DELETE CASCADE,
    notification_type VARCHAR(100) NOT NULL,
    template_id UUID REFERENCES notification_templates(id),
    data JSONB DEFAULT '{}',
    status VARCHAR(20) DEFAULT 'pending',
    sent_at TIMESTAMP,
    delivered_at TIMESTAMP,
    read_at TIMESTAMP,
    created_at TIMESTAMP DEFAULT NOW()
);

Production'a geçtikten üç ay sonra, o tablo aslında şöyle görünüyordu:

SQL
-- 20+ migration sonrası production gerçekliği
CREATE TABLE notification_events (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID, -- Performance sorunları nedeniyle foreign key kaldırıldı
    notification_type VARCHAR(100),
    notification_type_v2 VARCHAR(255), -- Migration devam ediyor
    template_id UUID,
    template_id_v2 BIGINT, -- Farklı takım farklı ID tipi kullandı
    data JSONB DEFAULT '{}',
    data_compressed BYTEA, -- JSONB çok büyüyünce eklendi
    status VARCHAR(20) DEFAULT 'pending',
    status_v2 VARCHAR(50), -- Beklenenden fazla status
    priority INTEGER DEFAULT 0, -- RFC'de yok, production için kritik
    retry_count INTEGER DEFAULT 0, -- RFC'de yok, debugging için şart
    channel VARCHAR(50), -- Query performance için denormalize edildi
    correlation_id UUID, -- Distributed tracing için eklendi
    partition_key INTEGER, -- Sharding için eklendi
    sent_at TIMESTAMP,
    delivered_at TIMESTAMP,
    read_at TIMESTAMP,
    failed_at TIMESTAMP, -- RFC'de yok, çok gerekli
    expires_at TIMESTAMP, -- RFC'de yok, sonsuz büyümeyi önledi
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW() -- Debugging kabuslarından sonra eklendi
);

-- Tahmin etmediğimiz 15 index
CREATE INDEX CONCURRENTLY idx_notification_events_user_created ON notification_events(user_id, created_at DESC) WHERE status != 'deleted';
CREATE INDEX CONCURRENTLY idx_notification_events_correlation ON notification_events(correlation_id) WHERE correlation_id IS NOT NULL;
-- ... ve 13 tane daha

Bu şema değişikliklerinin her biri bir production incident'ı, bir performance krizi veya tahmin edemediğimiz bir feature request'i temsil ediyor. Tertemiz RFC tasarımı gerçeklikle buluştu ve gerçeklik kazandı.

WebSocket Connection Management Felaketi#

RFC kendinden emin bir şekilde real-time bildirimleri WebSocket'lerle handle edeceğimizi söylüyordu. Örnek kod çok temiz görünüyordu:

TypeScript
// RFC'nin WebSocket implementation'ı
class NotificationWebSocketManager {
  private connections: Map<string, WebSocket> = new Map();
  
  async sendNotification(userId: string, notification: NotificationEvent) {
    const connection = this.connections.get(userId);
    if (connection && connection.readyState === WebSocket.OPEN) {
      connection.send(JSON.stringify({
        type: 'notification',
        data: notification
      }));
    }
  }
}

Altı ay sonra, mobil uygulama deployment'ının yanlış gittiği meşhur "50,000 zombie connection" felaketi dahil birden fazla production incident'ından sonra, elimizde aslında şu vardı:

TypeScript
// Tüm edge case'lerle production gerçekliği
class NotificationWebSocketManager {
  private connections: Map<string, Set<WebSocketConnection>> = new Map();
  private connectionMetadata: Map<string, ConnectionMetadata> = new Map();
  private healthChecks: Map<string, NodeJS.Timeout> = new Map();
  private rateLimiters: Map<string, RateLimiter> = new Map();
  private deadLetterQueue: Queue<FailedNotification>;
  private circuit: CircuitBreaker;
  
  async sendNotification(userId: string, notification: NotificationEvent) {
    // 200+ satır defensive programming
    const connections = this.connections.get(userId);
    if (!connections || connections.size === 0) {
      await this.queueForLaterDelivery(userId, notification);
      return;
    }
    
    // Kullanıcı başına birden fazla connection (mobile + web + tablet)
    const results = await Promise.allSettled(
      Array.from(connections).map(async (conn) => {
        try {
          // Connection sağlığını kontrol et
          if (!this.isConnectionHealthy(conn)) {
            await this.reconnectOrEvict(conn);
            throw new Error('Unhealthy connection');
          }
          
          // Connection başına rate limiting
          const limiter = this.getRateLimiter(conn.id);
          if (!await limiter.tryAcquire()) {
            await this.backpressure(conn, notification);
            return;
          }
          
          // Cascading failure'lar için circuit breaker
          return await this.circuit.fire(async () => {
            // Mesaj boyutu validasyonu (bunu zor yoldan öğrendik)
            const message = this.serializeNotification(notification);
            if (message.length > MAX_MESSAGE_SIZE) {
              const chunks = this.chunkMessage(message);
              for (const chunk of chunks) {
                await this.sendChunk(conn, chunk);
              }
            } else {
              await this.sendMessage(conn, message);
            }
          });
        } catch (error) {
          await this.handleDeliveryFailure(conn, notification, error);
        }
      })
    );
    
    // Delivery metriklerini takip et
    await this.recordDeliveryMetrics(userId, notification, results);
  }
  
  // Edge case'leri handle etmek için 50+ başka method
}

Bu eklemelerin her biri bir production incident'ından geldi. Circuit breaker? Redis cluster'ımızı çökerttiğimizde eklendi. Chunking logic? Gömülü resimli bir marketing bildirimi mobile client'ları çökerttiğinde keşfettik. Rate limiting? 100,000 support ticket üreten bir notification storm sırasında öğrendik.

Timeline vs Gerçeklik: 12 Hafta Yanılgısı#

RFC'nin aşamalı yaklaşımı çok mantıklı görünüyordu:

  • Phase 1 (Hafta 1-4): Core Infrastructure
  • Phase 2 (Hafta 5-8): Advanced Features
  • Phase 3 (Hafta 9-12): Integration & Optimization

Gerçekte olan:

Hafta 1-4: Infrastructure Sürprizi#

Core infrastructure inşa etmek yerine, üç hafta sadece environment'ları ayarlamak ve "standart" database setup'ımızın write throughput'u kaldıramadığını keşfetmekle geçti. 4. hafta tamamen başka bir sistemdeki production incident ile takımımızı çeken bir olayla tükendi.

Hafta 5-12: Scope Creep Senfonisi#

Product management, erken demo'ları görünce heyecanlandı. "Slack bildirimleri ekleyebilir miyiz?" Tabii, çok zor değil. "Kritik alert'ler için SMS?" Mantıklı. "Ah, bir de marketing kampanyaları için 90 gün önceden bildirim schedule etmeyi desteklememiz gerekiyor." Pardon, ne?

TypeScript
// Orijinal scope
const originalChannels = ['in_app', 'email', 'push'];

// 3. ay scope'u
const actualChannels = [
  'in_app', 
  'email', 
  'push', 
  'sms',          // Hafta 6'da eklendi
  'slack',        // Hafta 8'de eklendi
  'teams',        // Hafta 10'da eklendi
  'webhook',      // Hafta 11'de eklendi
  'discord',      // Hafta 14'te eklendi (evet, çoktan gecikmiştik)
  'voice_call'    // Hafta 20'de eklendi (kritik güvenlik alert'leri için)
];

Ay 4-6: Integration Cehennemi#

RFC'deki o temiz API tasarımını hatırlıyor musun? Tüm servislerimizin aynı authentication sistemini kullandığını varsayıyordu. Plot twist: kullanmıyorlardı. Üç farklı auth sistemimiz vardı (JWT, OAuth2 ve legacy session-based sistem) ve bildirimlerin hepsiyle çalışması gerekiyordu.

TypeScript
// RFC varsayımı
interface AuthContext {
  userId: string;
  token: string;
}

// Production gerçekliği
type AuthContext = 
  | { type: 'jwt'; userId: string; token: string; claims: JWTClaims }
  | { type: 'oauth2'; userId: string; accessToken: string; refreshToken: string; expiresAt: Date }
  | { type: 'legacy'; sessionId: string; userId?: string; cookieData: LegacyCookie }
  | { type: 'service_account'; serviceId: string; apiKey: string }
  | { type: 'anonymous'; temporaryId: string; ipAddress: string };

// Her auth tipi farklı handling, farklı rate limit, 
// farklı güvenlik kontrolleri ve farklı audit logging gerektiriyordu

Ay 7-8: Performance Hesaplaşması#

Sistem "çalışıyordu" ama production yükünü kaldıramıyordu. RFC saniyede 10,000 bildirim vaat ediyordu. Biz 500 ile uğraşıyorduk. Sonraki iki ay profiling, caching, query optimization ve mimari değişikliklerin bulanıklığıydı.

En büyük sürpriz? Darboğaz beklediğimiz yerde değildi (database write'ları). Template rendering'di. Süslü personalization sistemimiz kullanıcı context'i toplamak için bildirim başına 20+ API çağrısı yapıyordu.

Takım Dinamikleri: İnsan Faktörü#

RFC "12 hafta için 2 developer" diyordu. Gerçekte kim çalıştı:

  • 2 senior engineer (full-time olması gerekiyordu, production support yüzünden aslında %60)
  • 1 junior engineer (2. ayda eklendi, 3. ayı codebase'i öğrenmekle geçirdi)
  • 2 contractor (4. ayda "hızlı kazançlar" için eklendi, 5. ayı kodlarını düzeltmekle geçirdik)
  • 1 DevOps engineer (sözde "danışmanlık", 3. ayda aslında full-time)
  • 1 database uzmanı (5. ayda performance krizi için getirildi)
  • Product manager (proje boyunca iki kez değişti)
  • 3 farklı engineering manager (6. ayda reorg oldu)

Her takım değişikliği context kaybı, mimari tartışmaları ve yeniden çalışma anlamına geliyordu. Contractor kodu code review'da iyi görünüyordu ama hala ödediğimiz teknik borç yarattı. 6. aydaki reorg, yeni engineering manager "mimariyi yeniden gözden geçirmek" istediğinde projeyi neredeyse öldürüyordu.

Kimsenin Bahsetmediği Monitoring Boşluğu#

RFC'nin monitoring üzerine bir bölümü vardı. Delivery rate, response time ve error rate gibi metrikleri listeliyordu. Mantıklı, değil mi? Production'da gerçekten monitor etmemiz gerekenler:

TypeScript
// RFC monitoring planı
const plannedMetrics = [
  'delivery_rate',
  'response_time', 
  'error_rate',
  'throughput'
];

// Gerçekte monitor ettiğimiz
const productionMetrics = [
  // Temel metrikler (RFC'den)
  'delivery_rate_by_channel_by_priority_by_user_segment',
  'response_time_p50_p95_p99_p999',
  'error_rate_by_type_by_service_by_retry_count',
  
  // Gerçekten önemli olan metrikler
  'template_render_time_by_template_by_variables_count',
  'database_connection_pool_wait_time',
  'redis_operation_time_by_operation_type',
  'webhook_retry_backoff_effectiveness',
  'notification_staleness_at_delivery',
  'user_preference_cache_hit_rate',
  'deduplication_effectiveness_by_time_window',
  'rate_limit_rejection_by_reason',
  'circuit_breaker_state_transitions',
  'message_size_distribution_by_channel',
  'websocket_reconnection_storms',
  'push_token_invalidation_rate',
  'email_bounce_classification',
  'notification_feedback_loop_latency',
  'cost_per_notification_by_channel',
  'regulatory_compliance_audit_completeness',
  
  // Spesifik incident'lardan sonra ihtiyaç duyduğumuz garip metrikler
  'mobile_app_version_vs_notification_compatibility',
  'timezone_calculation_accuracy',
  'emoji_rendering_failures_by_client',
  'notification_delivery_during_database_failover',
  'memory_leak_in_template_cache',
  'thundering_herd_detection'
];

Bu metriklerin her biri bir şeyler ters gittiği için var ve çok geç olana kadar geleceğini görmedik.

Teknik Borç: Kısayolların Bileşik Faizi#

RFC teknik borçtan bahsetmiyordu. 8. aya geldiğimizde uğraştığımız şey:

Template System Frankenstein'ı#

Basit bir template sistemi ile başladık. Production'a geldiğimizde, farklı takımların farklı gereksinimleri olduğu ve hiç birleştirmeye vaktimiz olmadığı için aynı anda çalışan üç farklı template engine'imiz vardı.

TypeScript
// Hala ödediğimiz teknik borç
class NotificationTemplateManager {
  private mustacheTemplates: Map<string, MustacheTemplate>;    // Orijinal sistem
  private handlebarsTemplates: Map<string, HandlebarsTemplate>; // Marketing için eklendi
  private reactEmailTemplates: Map<string, ReactEmailTemplate>; // Güzel email'ler için eklendi
  
  async render(templateId: string, data: any): Promise<string> {
    // Hangi template engine'i kullanacağını bulmak,
    // edge case'leri handle etmek, backward compatibility sağlamak,
    // ve production'ı bozmadan düzeltemediğimiz bug'ları atlatmak için
    // 150 satır logic
    
    // Bu yorum 4. aydan beri burada:
    // TODO: Template sistemlerini birleştir (tahmini: 2 hafta)
    // Araştırmadan sonraki gerçek tahmin: 3 ay + migration planı
  }
}

Hiç Bitmeyen Migration#

O güzel database şemasını hatırlıyor musun? Altı aydır "v2"ye migrate ediyoruz. Her iki şemayı da paralel çalıştırıyoruz, ara sıra bildirim kaybeden karmaşık bir sync sistemi ile.

SQL
-- Migration kabusu
BEGIN;
  -- Migration planındaki 47 adımdan 1. adım
  INSERT INTO notification_events_v2 
  SELECT 
    id,
    user_id,
    -- 50 satır karmaşık transformation logic
    CASE 
      WHEN notification_type IN ('old_type_1', 'old_type_2') THEN 'new_type_1'
      WHEN notification_type LIKE 'legacy_%' THEN REPLACE(notification_type, 'legacy_', 'classic_')
      -- 20 WHEN clause daha
    END as notification_type_v2,
    -- Daha fazla transformation...
  FROM notification_events 
  WHERE created_at > NOW() - INTERVAL '1 hour'
    AND status != 'migrated'
    AND NOT EXISTS (
      SELECT 1 FROM notification_events_v2 
      WHERE notification_events_v2.id = notification_events.id
    );
  
  -- Migration status'ü güncelle
  UPDATE migration_status 
  SET last_run = NOW(), 
      records_migrated = records_migrated + row_count,
      estimated_completion = NOW() + (remaining_records / current_rate * INTERVAL '1 second')
  WHERE migration_name = 'notification_schema_v2';
  
  -- Conflict'leri kontrol et
  -- Rollback senaryolarını handle et
  -- Monitoring metriklerini güncelle
  -- 100 satır daha...
COMMIT;

Önemli Olmayan Success Metrikleri#

RFC net success kriterleri tanımlamıştı: %99.9 uptime, <100ms delivery, saniyede 10,000 bildirim. Sonunda bu sayılardan bazılarına ulaştık, ama yanlış metrikler olduğu ortaya çıktı.

Aslında önemli olan:

  • Kullanıcı mutluluğu: %99 delivery rate'imiz vardı ama kullanıcılar kötü zamanlandığı için bildirimleri sevmiyordu
  • Developer verimliliği: Diğer takımlar "temiz" API'mıza kapsamlı yardım olmadan entegre olamıyordu
  • Operasyonel yük: Tüm otomasyonumuza rağmen sistem sürekli bakım gerektiriyordu
  • İş değeri: Marketing özelliklerin yarısını kullanamıyordu çünkü çok karmaşıktı
TypeScript
// Optimize ettiğimiz (RFC'den)
const technicalMetrics = {
  uptime: 99.9,
  deliveryTime: 95, // ms
  throughput: 10000, // saniyede
  errorRate: 0.1 // yüzde
};

// Aslında önemli olan
const businessMetrics = {
  userNotificationDisableRate: 45, // yüzde - çok yüksek
  developerIntegrationTime: 3, // hafta - saat olmalı
  supportTicketsPerWeek: 150, // bildirimlerle ilgili
  marketingCampaignSetupTime: 2, // gün - dakika olmalı
  monthlyOperationalCost: 25000, // dolar - tahminin 5 katı
  engineersPagedPerWeek: 12 // kez - sürdürülemez
};

Zor Yoldan Öğrenilen Dersler#

Birden fazla şirkette bildirim sistemleri implement ettikten, RFC'lerin gerçeklikle çarpışmasını izledikten sonra öğrendiklerim:

1. RFC'ler Hipotez, Spesifikasyon Değil#

RFC'ni başlangıç hipotezi olarak değerlendir. Production'a çarptığı an, sürekli revizyon gerektiren yaşayan bir belge haline gelir. RFC'mizi çok uzun süre "spec" olarak dondurmuştuk, gerçeklik ayrıldığında sonsuz karışıklığa neden oldu.

2. Bilinmeyen Bilinmeyenler İçin Bütçe Ayır#

Timeline ve bütçen ne olursa olsun, iki katına çıkar, sonra bilmediğini bilmediğin şeyler için %50 ekle. Bu kötümserlik değil; düzinelerce projeden pattern tanıma.

3. İlk Günden Migration İçin Tasarla#

Her güzel şema migration'a ihtiyaç duyacak. Her temiz API versiyonlamaya ihtiyaç duyacak. Her basit sistem backward compatibility'ye ihtiyaç duyacak. Bu yetenekleri baştan inşa et, sonradan düşünme.

4. Edge Case'ler Norm'dur#

RFC review'da tartıştığın o edge case? Herkesin "ortaya çıkarsa hallederiz" dediği? Ortaya çıkacak, muhtemelen production'da, muhtemelen en kötü zamanda. Tartışıyorsan, edge case değildir.

5. Organizasyonel Dinamikler Teknik Mükemmelliği Yener#

En iyi teknik tasarım takım dinamiklerini, politik gerçeklikleri ve organizasyonel kısıtlamaları hesaba katmazsa başarısız olur. 3. ayda katılan contractor senin güzel mimarini umursamıyor. 6. aydaki reorg her kararı sorgulayacak.

6. Gerçekten Debug Edeceğin Şeyleri Monitor Et#

RFC'nin söylediğini monitor etme. Her şey yanarken bir incident sırasında neye ihtiyacın olacağını monitor et. Bu business metrikler, kullanıcı deneyimi metrikleri ve detaylı operasyonel metrikler demek, sadece teknik istatistikler değil.

İleriye Giden Yol: Tasarım ve Gerçeklik Arasında Köprü Kurma#

Peki RFC ile production arasındaki boşluğu nasıl kapatırız? Yıllarca farklı yaklaşımları denedikten sonra, gerçekten işe yarayan:

Minimum Sevilebilir Ürün ile Başla#

Minimum yaşayabilir değil – minimum sevilebilir. Kullanıcıların gerçekten kullanmak istediği küçük bir şey inşa et, sonra iterate et. Bildirim sistemimiz her şeyi bir kerede inşa etmeye çalışmak yerine sadece mükemmel çalışan email bildirimleri ile başlasaydık daha iyi olurdu.

Mükemmellik İçin Değil, Değişim İçin Tasarla#

Database şeman değişecek. API'n evrimleşecek. Mimarin kayacak. Mükemmel son durumu tahmin etmeye çalışmak yerine zarif bir şekilde evrimleşebilen sistemler tasarla.

Developer Experience'a Erken Yatırım Yap#

Sistemin entegre edilmesi ve işletilmesi ne kadar kolaysa, o kadar başarılı olur. Performance optimize ederken aylar harcadık, API'yi kullanımı kolaylaştırıyor olmalıydık.

Yaşayan Dokümantasyon Oluştur#

O RFC tarihi bir eser olmamalı. Sistemle birlikte evrimleşmeli. Artık RFC'lerimizi "Orijinal Tasarım", "Mevcut Implementation" ve "Öğrenilen Dersler" bölümleriyle yaşayan belgeler olarak tutuyoruz.

Her Seviyede Feedback Loop'ları İnşa Et#

Kullanıcı feedback'inden operasyonel metriklere, developer deneyim anketlerine kadar, sürecine feedback loop'ları inşa et. Neyin çalışmadığını ne kadar hızlı öğrenirsen, o kadar hızlı düzeltebilirsin.

Sonuç: Karmaşıklığı Kucaklamak#

20 yıl sistem inşa ettikten sonra, karmaşıklığı kucaklamayı öğrendim. O tertemiz RFC karmaşık hale gelecek. Güzel mimarin siğiller çıkaracak. Temiz codebase'in teknik borç biriktirecek. Bu başarısızlık değil – gerçek kullanıcılar için gerçek problemleri çözen sistemlerin doğal evrimi.

RFC ile production arasındaki boşluk ortadan kaldırılacak bir şey değil; yönetilecek bir şey. En iyi engineer'lar her detayı tahmin eden mükemmel RFC'ler yazan değil. Gerçeklik kaçınılmaz olarak plandan saptığında adapte olabilenler.

Bildirim sistemimize geri baktığımda, RFC'de tasarladığımız şey değil. Daha karmaşık, daha kompleks ve inşa etmesi üç kat daha uzun sürdü. Ama aynı zamanda daha yetenekli, daha dayanıklı ve o RFC'yi yazdığımızda var olduğunu bile bilmediğimiz problemleri çözüyor.

Bir dahaki sefere RFC yazarken hatırla: bir spesifikasyon yazmıyorsun, gerçeklikle bir konuşma başlatıyorsun. Ve gerçeklik her zaman son sözü söyler.

RFC'den production'a geçiş boşluğunu deneyimledin mi? Güzel tasarımların karmaşık gerçeklikle buluşmasını izlerken hangi dersleri öğrendin? War story'lerini ve bir dahaki sefere neyi farklı yapacağını duymak isterim.

Loading...

Yorumlar (0)

Sohbete katıl

Düşüncelerini paylaşmak ve toplulukla etkileşim kurmak için giriş yap

Henüz yorum yok

Bu yazı hakkında ilk düşüncelerini paylaşan sen ol!

Related Posts