← Geri
YazılımEnes Ünlüer

Getmobil’de Turborepo’ya Geçiş: Monorepo ile Hız, Tutarlılık ve Ölçek

Bir noktadan sonra “kod yazmak” kadar “kodun etrafındaki sistem” de ürüne dönüşüyor: derleme süreleri, CI maliyeti, ortak bileşenler, tutarlı kurallar, güvenli refactor, onboarding… Getmobil’de bu ihtiyaçlar büyümeye başlayınca, frontend tarafında Turborepo tabanlı bir monorepo geçişini ben sahiplendim ve uçtan uca yönettim.

Bu yazıda, çok proje detayına girmeden:

  • Neden bu kararı aldığımızı,
  • Geçişi nasıl tasarladığımı ve adım adım nasıl yürüttüğümü,
  • Teknik ve mimari kazanımları,
  • Liderlik tarafında neleri doğru/yanlış yaptığımı paylaşacağım.

Not: İsimleri/örnekleri bilinçli olarak genelleştiriyorum. Odak: yaklaşım, mimari ve karar kalitesi.


Neden Turborepo? Sorun “monorepo” değil, ölçeklenebilirlik

Frontend büyüdükçe şu sinyaller belirginleşiyor:

  • Aynı işin farklı yerlerde tekrar edilmesi: ESLint/TS config, API client, UI parçaları, helper’lar…
  • Tutarsız kalite standardı: Bir app’de çözülen bir problem diğerinde tekrar yaşanıyor.
  • Yavaş geri bildirim döngüsü: Lint/type-check/build adımları, özellikle CI’da pahalı.
  • Refactor korkusu: Paylaşımlı değişikliklerin etkisini görmek zor ve riskli.
  • Onboarding maliyeti: “Hangi repo? Hangi versiyon? Hangi kural?” soruları.

Benim için kritik fark şuydu: Bu problemler tek tek araçlarla pansuman olabiliyor ama kök neden “parçalı yapının koordinasyon maliyeti”. Turborepo burada iki şeyi aynı anda çözüyor:

  1. Monorepo disiplinini iyi bir görev orkestrasyonu ile pratik hale getiriyor.
  2. Doğru kurgulanırsa, ekiplerin hızını artırırken kaliteyi düşürmüyor (hatta artırıyor).

Hedefleri netleştirmek: “Tek seferde büyük geçiş” değil, kontrollü evrim

Geçişi başlatmadan önce, ekip ve paydaşlarla şu hedefleri netleştirdim:

  • DX (Developer Experience): Lokal geliştirmede hızlı geri bildirim
  • CI süreleri ve maliyet: Gereksiz iş yapan pipeline’ı kırpmak
  • Paylaşılabilirlik: UI, types, hooks, lib, api gibi parçaları merkezileştirmek
  • Güvenli değişim: Tip güvenliği + standartlar + bağımlılık sınırları
  • Kademeli geçiş: Big-bang değil; geri dönüşü olan adımlar

Bu hedefler önemli çünkü Turborepo’yu “kurduk bitti” diye görürseniz, birkaç hafta sonra monorepo “spagetti”ye dönebiliyor. Ben bunu bir mimari ürün gibi ele aldım: kural seti + governance + geliştirici ergonomisi.


Mimari tasarım: Apps + Packages, net sınırlar

Monorepo’nun başarısı çoğu zaman “nerede hangi kod yaşamalı?” sorusuna verdiğiniz cevaptır.

Getmobil’de yaklaşımı şu prensiplerle kurguladım:

1) Paylaşılan her şey packages/ altında

Ama “her şeyi paket yapalım” değil; paket sayısını anlamlı tuttuk:

  • api: network client ve response tipleri için tek kaynak
  • types: domain modelleri
  • ui / orange-design: tasarım sistemi ve ortak UI
  • hooks / lib: reusable hook ve saf utility’ler

2) Uygulama özel kodlar apps/ içinde

Uygulamalar kendi feature’larını taşır; ortak ihtiyaç paketleşir. Bu ayrım, “ortak kod”un sahipliğini netleştirip refactor’u kolaylaştırıyor.

3) “Sınır ihlali”ni kültür değil, sistem engeller

Sadece “lütfen import etmeyin” demek yetmez.

  • dependency yönünü doğru kurmak,
  • lint kurallarıyla boundary enforcement yapmak,
  • PR review checklist’i ile pekiştirmek gerekiyor.

Uygulama stratejisi: Kademeli, ölçülebilir, geri dönüşlü

Benim uyguladığım model, üç fazlıydı:

Faz 1 - Altyapı ve ortak kurallar

  • pnpm workspace düzeni
  • Tekil TypeScript/ESLint/Prettier standardı
  • turbo.json ile görev graph’ı (build/lint/type-check)
  • Paketlerin iskeleti (api/types/ui/lib/hooks)

Amaç: “repo açıldığında herkesin aynı şekilde çalışması”.

Örnek (genelleştirilmiş) turbo.json yaklaşımı:

{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "lint": { "cache": true },
    "type-check": { "cache": true },
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    }
  }
}

Burada kritik nokta: önce task graph’ı, sonra code movement.

Faz 2 - Ortak parçaların taşınması (de-risk)

  • Tipler (types) tek kaynağa alındı
  • API client (api) standardize edildi
  • UI ve design parçaları paketlere taşındı

Bu fazda, uygulamaları minimum düzeyde kırarak “değer üretmeye” başladık:

  • Aynı tipin farklı repo/uygulamada farklı tanımlanması bitti
  • API entegrasyonlarında tutarlılık arttı
  • UI parçalarının reuse oranı yükseldi

Faz 3 - Uygulama içi sadeleşme ve governance

  • App-local “mini lib”lerin azaltılması
  • Import yönlerinin netleştirilmesi
  • Dokümantasyon + örnek PR’lar
  • “Yeni paket açma kriterleri” (paket enflasyonunu engellemek için)

Teknik kazanımlar: “Hız” tek başına değil, öngörülebilirlik kazanımı

Turborepo’yu iyi kılan şey sadece caching değil; işi doğru yerde yapmaya zorlaması. Benim gözlemlediğim teknik kazanımlar:

  1. Daha hızlı ve daha akıllı CI
  • Değişmeyen paketler için build/lint/type-check tekrar edilmez
  • Sadece etkilenen parçalar çalışır (iş akışı daha deterministik olur)
  1. Tek kaynak, daha az “drift”
  • ESLint/TS ayarları tek yerden yönetilir
  • API ve types tek kaynaktan gelir
  • “Bu projede böyleydi, diğerinde şöyleydi” tartışmaları azalır
  1. Refactor yapabilme cesareti

Monorepo + TypeScript birleşince:

  • Bir değişikliğin etkisi aynı PR’da görünür
  • Tip hataları, kırılmayı “runtime”dan “compile-time”a çeker
  • Büyük değişiklikler parça parça yapılabilir
  1. Modüler mimariyi zorlayan yapı

Packages katmanı, ekibi doğal olarak şu soruya iter: “Bu kod gerçekten uygulamaya mı ait, yoksa paylaşımlı mı?” Bu da uzun vadede mimariyi temizler.


Liderlik ve süreç: Benim için asıl iş “teknik” değil, hizalama

Bu tarz geçişler genellikle şu yüzden batıyor: “Teknik kurulum” tamam ama ekipler aynı anda hareket etmiyor.

Benim odaklandığım liderlik başlıkları:

  1. Kararı “satmak” değil, birlikte üretmek
  • Sorunları görünür kıldım (CI süreleri, tekrar eden kod, onboarding)
  • Alternatifleri masaya koydum (status quo / partial share / monorepo)
  • Seçimi trade-off’larıyla anlattım: monorepo her derde deva değil
  1. Riskleri küçük parçalara böldüm
  • Big-bang yerine kademeli plan
  • Geri dönüşlü adımlar (feature freeze gerektirmeyen)
  • İlk etapta “güvenli” paketler (types/api/ui) ile değer üretme
  1. Standartları “doküman” değil, “geliştirici deneyimi” yaptım
  • Kurallar kısa, uygulanabilir, örnekli
  • Yeni gelen biri “doğru yolu” kolayca buluyor
  • Review süreçlerinde “kişisel yorum” azalıyor, standart konuşuyor
  1. Sahiplik modeli kurdum

Monorepo’da “herkes her şeyi yapar” yaklaşımı kaos getirir.

  • Paket sahipliği (owners) net olmalı
  • API/types değişikliklerinde kalite kapıları olmalı
  • UI değişikliklerinde geriye dönük uyumluluk gözetilmeli

Zorlandığım yerler ve dersler

Bu bölüm, benzer bir geçiş düşünenler için en değerli kısım olabilir:

  • Paket enflasyonu: Her şeyi paketleştirmek yerine, anlamlı sınırlar koymak gerekiyor.
  • Circular dependency riski: Bağımlılık yönlerini erken tasarlamak şart.
  • “Herkesin işi” tuzağı: Sahiplik yoksa monorepo hızlı kirleniyor.
  • Kısa vadeli baskı: “Şimdi feature yetişsin” baskısı altında mimari borcu ertelemek kolay. Burada net önceliklendirme ve iletişim kritik.

Sonuç: Daha hızlı teslimat değil, daha sürdürülebilir hız

Getmobil’de Turborepo’ya geçiş benim için bir “tooling” projesi değil; mühendislik organizasyonunu hızlandıran bir mimari yatırım oldu.

Kazanım, sadece dakika/saniye değil:

  • daha öngörülebilir delivery,
  • daha güvenli değişim,
  • daha az tekrar,
  • daha tutarlı kalite,
  • daha kolay ölçeklenen bir frontend platformu.

Eğer benzer bir geçişi düşünüyorsanız, tek tavsiyem şu: Turborepo’yu kurmak kolay; monorepo’yu yaşatmak liderlik ister.