Skip to content

Sürüm Yönetimi ​

Mobil uygulamalarda sürüm yönetimi, kullanıcıya kesintisiz deneyim sunmak ve uygulamanın yaşam döngüsünü etkili şekilde yönetmek için kritik öneme sahiptir. Bu dokümantasyon, modern sürüm yönetimi stratejilerini ve CI/CD entegrasyonunu kapsar.

Hızlı Karar ​

İhtiyaçYaklaşımDikkat
Düzenli releaseRelease trainAcil hotfix yolu ayrı olmalı
Riskli değişiklikStaged rolloutHalt criteria net olmalı
Zorunlu güncellemeMinimum version gateKullanıcı etkisi yüksek
Çok platformOrtak release checklistStore farkları hesaba katılmalı

Üretim Kontrol Listesi ​

  • Problem: Release süreci hangi kalite veya koordinasyon riskini azaltıyor?
  • Çözüm: Versioning, changelog, signing, test gate, rollout ve support iletişimi net mi?
  • Trade-off: Daha kontrollü release güven verir; hız ve operasyon maliyeti getirir.
  • Hata durumu: Wrong build, missing symbols, signing failure, store rejection ve bad rollout ele alınmalı.
  • Ölçüm: Release lead time, adoption, crash-free sessions, rollback count ve support ticket delta izlenmeli.
  • Güvenlik/maliyet: Signing key erişimi sıkı olmalı; release artefact'leri izlenebilir tutulmalı.

Semantic Versioning ​

Versiyon Numaralandırma ​

MAJOR.MINOR.PATCH
Örnek: 2.1.3

MAJOR: Geriye dönük uyumsuz değişiklikler
MINOR: Geriye dönük uyumlu yeni özellikler
PATCH: Geriye dönük uyumlu hata düzeltmeleri

iOS App Store Sürümleme ​

swift
// Info.plist
<key>CFBundleShortVersionString</key>
<string>2.1.3</string>
<key>CFBundleVersion</key>
<string>23</string>

// Sürüm yönetimi
struct AppVersion {
    static let marketing = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
    static let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown"
    static let fullVersion = "\(marketing) (\(build))"
    
    static func isNewerVersion(_ version: String) -> Bool {
        return marketing.compare(version, options: .numeric) == .orderedAscending
    }
}

Android Play Store Sürümleme ​

kotlin
// build.gradle.kts
android {
    defaultConfig {
        versionCode = 23
        versionName = "2.1.3"
    }
}

// Sürüm yönetimi
object AppVersion {
    val versionName: String
        get() = BuildConfig.VERSION_NAME
    
    val versionCode: Int
        get() = BuildConfig.VERSION_CODE
    
    fun isNewerVersion(version: String): Boolean {
        return versionName.compareTo(version, ignoreCase = true) < 0
    }
}

Sürüm Pipeline ​

yaml
# GitHub Actions - Release Pipeline
name: Release Pipeline
on:
  push:
    tags:
      - 'v*'

jobs:
  build-ios:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup Xcode
        uses: maxim-lobanov/setup-xcode@v1
        with:
          xcode-version: latest-stable
      - name: Build iOS
        run: |
          xcodebuild -workspace App.xcworkspace \
                     -scheme App \
                     -configuration Release \
                     -archivePath App.xcarchive \
                     archive
      - name: Export IPA
        run: |
          xcodebuild -exportArchive \
                     -archivePath App.xcarchive \
                     -exportPath ./build \
                     -exportOptionsPlist ExportOptions.plist
      - name: Upload to App Store
        uses: apple-actions/upload-testflight-build@v1
        with:
          app-path: './build/App.ipa'
          issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
          api-key-id: ${{ secrets.APPSTORE_API_KEY_ID }}
          api-private-key: ${{ secrets.APPSTORE_API_PRIVATE_KEY }}
  build-android:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup Java
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'
      - name: Build Android
        run: ./gradlew assembleRelease
      - name: Sign APK
        uses: r0adkll/sign-android-release@v1
        with:
          releaseDirectory: app/build/outputs/apk/release
          signingKeyBase64: ${{ secrets.SIGNING_KEY }}
          alias: ${{ secrets.ALIAS }}
          keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
          keyPassword: ${{ secrets.KEY_PASSWORD }}
      - name: Upload to Play Store
        uses: r0adkll/upload-google-play@v1
        with:
          serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
          packageName: com.example.app
          releaseFiles: app/build/outputs/apk/release/app-release-signed.apk
          track: production

Kademeli Dağıtım ​

Kademeli Yayın Stratejisi ​

swift
// iOS Kademeli Yayın
class ReleaseManager {
    func checkRolloutEligibility() -> Bool {
        let userId = UserDefaults.standard.string(forKey: "user_id") ?? ""
        let userHash = userId.sha256()
        let rolloutPercentage = RemoteConfig.shared.getInt("rollout_percentage", defaultValue: 0)
        let userBucket = Int(userHash.prefix(8), radix: 16) ?? 0
        return (userBucket % 100) < rolloutPercentage
    }
    func shouldShowNewFeature() -> Bool {
        return checkRolloutEligibility() && AppVersion.isNewerVersion("2.1.0")
    }
}
extension String {
    func sha256() -> String {
        let data = Data(self.utf8)
        let hash = SHA256.hash(data: data)
        return hash.compactMap { String(format: "%02x", $0) }.joined()
    }
}

A/B Test Entegrasyonu ​

kotlin
class ReleaseExperimentManager {
    private val remoteConfig = Firebase.remoteConfig
    fun isUserInExperiment(experimentName: String): Boolean {
        val experimentConfig = remoteConfig.getString("experiment_$experimentName")
        val config = JSONObject(experimentConfig)
        val enabled = config.getBoolean("enabled")
        if (!enabled) return false
        val percentage = config.getInt("percentage")
        val userId = getUserId()
        return getUserBucket(userId) < percentage
    }
    private fun getUserBucket(userId: String): Int {
        val hash = userId.toByteArray().fold(0) { acc, byte -> 31 * acc + byte.toInt() }
        return kotlin.math.abs(hash % 100)
    }
    fun trackExperimentExposure(experimentName: String, variant: String) {
        FirebaseAnalytics.getInstance(context).logEvent("experiment_exposure") {
            param("experiment_name", experimentName)
            param("variant", variant)
            param("user_id", getUserId())
        }
    }
}

Modern sürüm yönetimi, otomasyon, kademeli dağıtım ve analitik entegrasyonu ile güvenli ve esnek mobil uygulama yayınları sağlar.

Eren Demir tarafından oluşturulmuştur.