Sparround

İki implementasiya: remote, local və fake (flavor-lar)

Abstract repository-nin əsl səbəbi budur: bir müqavilə, bir neçə implementasiya. Rəsmi tövsiyələr bunu belə formalaşdırır — abstract repository sinifləri müxtəlif mühitlər üçün müxtəlif implementasiya yazmağa imkan verir.

Praktikada dörd implementasiya növü olur:

  • `...Remote` — məhsul rejimi: real API.
  • `...Local` — lokal baza ya da hazır məlumat: demo rejimi, offline nümayiş, backend hazır olmayanda paralel iş.
  • `...Fake` (test qovluğunda) — yaddaşda saxlanan siyahı: unit və widget testləri.
  • `...Delegating` (nadir) — bir neçə implementasiyanı şəraitə görə seçən qat.

Bu quruluşun ən dəyərli tərəfi komanda işidir: backend endpoint-i hazır olmadıqda mobil developer ...Local implementasiyası ilə bütün ekranı, bütün state-ləri və bütün xəta hallarını qurur. Endpoint gəldikdə yalnız provider-in bir sətri dəyişir.

dart
// ══ Müqavilə (domain) ══
abstract interface class ProductRepository {
  Future<List<Product>> fetchActive();
  Future<Product> fetchById(String id);
}

// ══ 1. Məhsul rejimi ══
class ProductRepositoryRemote implements ProductRepository {
  ProductRepositoryRemote({required ProductApiClient apiClient})
      : _apiClient = apiClient;
  final ProductApiClient _apiClient;

  @override
  Future<List<Product>> fetchActive() async =>
      (await _apiClient.getActiveProducts())
          .map((dto) => dto.toDomain())
          .toList();

  @override
  Future<Product> fetchById(String id) async =>
      (await _apiClient.getProduct(id)).toDomain();
}

// ══ 2. Demo / backend hazır olmayanda ══
class ProductRepositoryLocal implements ProductRepository {
  ProductRepositoryLocal({
    this.latency = const Duration(milliseconds: 600),
    this.failureRate = 0,
  });

  /// Real şəbəkə gecikməsini imitasiya edir — loading state-ini
  /// GÖRMƏK üçün vacibdir, əks halda spinner heç vaxt görünmür.
  final Duration latency;

  /// 0..1 — xəta ehtimalı. Xəta ekranını sınamaq üçün.
  final double failureRate;

  static const _seed = [
    Product(id: 'p1', title: 'Yaşıl çay', price: 8.5, discountPercent: 10),
    Product(id: 'p2', title: 'Qara çay', price: 6),
    Product(id: 'p3', title: 'Arxivdə', price: 4, isArchived: true),
  ];

  @override
  Future<List<Product>> fetchActive() async {
    await Future<void>.delayed(latency);
    if (_shouldFail()) throw const NetworkFailure();
    return _seed.where((p) => !p.isArchived).toList();
  }

  @override
  Future<Product> fetchById(String id) async {
    await Future<void>.delayed(latency);
    if (_shouldFail()) throw const NetworkFailure();
    return _seed.firstWhere(
      (p) => p.id == id,
      orElse: () => throw const NotFoundFailure(),
    );
  }

  bool _shouldFail() =>
      failureRate > 0 && Random().nextDouble() < failureRate;
}

// ══ 3. Test (test/fakes/) ══
class FakeProductRepository implements ProductRepository {
  FakeProductRepository(this.products, {this.error});
  final List<Product> products;
  final Object? error;                 // xəta yolunu sınamaq üçün
  int fetchActiveCount = 0;

  @override
  Future<List<Product>> fetchActive() async {
    fetchActiveCount++;
    if (error != null) throw error!;
    return products;                   // gecikmə YOX — test sürətli olsun
  }

  @override
  Future<Product> fetchById(String id) async {
    if (error != null) throw error!;
    return products.firstWhere((p) => p.id == id);
  }
}

Bir müqavilə, üç implementasiya. `...Local` yalnız demo üçün deyil — xəta və gecikmə hallarını da simulyasiya edir.

dart
// ══ lib/main.dart ══ (məhsul rejimi)
void main() {
  runApp(const ProviderScope(child: MyApp()));
}

// ══ lib/main_development.dart ══ (lokal məlumatla)
void main() {
  runApp(
    ProviderScope(
      overrides: [
        // Bütün data qatı bir siyahı ilə əvəz olunur.
        productRepositoryProvider.overrideWithValue(
          ProductRepositoryLocal(
            latency: const Duration(milliseconds: 800),
            failureRate: 0.2,        // hər 5 sorğudan biri xəta
          ),
        ),
        orderRepositoryProvider.overrideWithValue(
          OrderRepositoryLocal(seed: demoOrders),
        ),
      ],
      child: const MyApp(),
    ),
  );
}

// ══ lib/main_staging.dart ══ (real API, başqa baseUrl)
void main() {
  runApp(
    ProviderScope(
      overrides: [
        apiBaseUrlProvider.overrideWithValue('https://staging.example.com'),
        // Ödəniş isə staging-də də fake olur — real pul hərəkəti olmasın.
        paymentRepositoryProvider.overrideWithValue(
          PaymentRepositoryFake(alwaysSucceed: true),
        ),
      ],
      child: const MyApp(),
    ),
  );
}

// İşə salmaq:
//   flutter run -t lib/main_development.dart
//   flutter run -t lib/main_staging.dart --flavor staging
//
// DİQQƏT: bütün overrides ProviderScope-dadır, tətbiq kodunda
// heç bir `if (isDev)` şərti yoxdur — bu, ən vacib nəticədir.

Flavor-lar: rəsmi case study ayrı giriş nöqtələri işlədir (`main_development.dart`, `main_staging.dart`). Riverpod-da fərq `overrides` siyahısındadır.

İmplementasiyaYeriNə vaxt işlədilirXüsusiyyəti
`...Remote``lib/data/repositories/`Məhsul rejimiReal service-lər, cache, retry
`...Local``lib/data/repositories/`Demo, offline nümayiş, backend gözlənilərkənGecikmə və xəta ehtimalı konfiqurasiya olunur
`...Fake``test/fakes/`Unit və widget testləriGecikmə yoxdur; çağırış sayı sayılır
`...Delegating``lib/data/repositories/`Şəraitə görə seçim (məsələn feature flag)Nadir — çox vaxt DI səviyyəsində həll olunur

...Local implementasiyasında gecikmə və xəta ehtimalını konfiqurasiya etmək kiçik detal kimi görünür, lakin praktik dəyəri böyükdür: loading spinner-i, boş siyahı ekranı, xəta ekranı və retry düyməsi yalnız bu şəkildə real olarak sınanır. Şəbəkə dərhal cavab verdiyində loading state-i heç vaxt görünmür və onun baqları məhsul rejiminə qədər gizli qalır.

Praktika. Bir repository üçün ...Local implementasiyası yaz (gecikmə + xəta ehtimalı ilə) və lib/main_development.dart giriş nöqtəsi yarat. Sonra tətbiqi flutter run -t lib/main_development.dart ilə işə sal və üç halı öz gözünlə yoxla: loading, boş siyahı, xəta + retry.

Hazır sayılır: üç hal da real ekranda görünür və tətbiq kodunda heç bir if (isDev) şərti yoxdur.

📚 Mənbələr və sənədlər