Use-case: nə vaxt lazımdır, nə vaxt zərərlidir
Use-case (digər adı interactor) — bir biznes əməliyyatını təmsil edən sinif: "səbətin yekununu hesabla", "sifarişi ver", "profili abunə statusu ilə birlikdə yüklə".
Rəsmi Flutter bələdçisi use-case-i opsional qat sayır və onu yalnız üç şərtdən biri ödəndikdə tövsiyə edir:
1. Əməliyyat bir neçə repository-dən məlumat tələb edir. 2. Məntiq həqiqətən mürəkkəbdir. 3. Eyni məntiq bir neçə view model tərəfindən istifadə olunur.
Bələdçi qazancı və qiyməti də açıq sayır.
Qazanc: view model-lərdə təkrarın qarşısını alır, mürəkkəb biznes məntiqini UI məntiqindən ayırdığı üçün testi asanlaşdırır, view model-i oxunaqlı saxlayır.
Qiymət: arxitekturanın mürəkkəbliyi artır (daha çox sinif, daha çox koqnitiv yük), test üçün əlavə mock-lar lazım gəlir, boilerplate artır.
Bələdçinin tövsiyəsi birbaşadır: use-case-ləri yalnız lazım olduqda əlavə et. Sonradan view model-lərin çoxu use-case vasitəsilə məlumat almağa başlayırsa, o zaman hamısını use-case-ə keçirmək olar.
// fayl: lib/domain/usecases/calculate_cart_total.dart
import 'package:my_app/domain/models/cart.dart';
import 'package:my_app/domain/models/cart_total.dart';
import 'package:my_app/domain/repositories/cart_repository.dart';
import 'package:my_app/domain/repositories/coupon_repository.dart';
import 'package:my_app/domain/repositories/delivery_repository.dart';
class CalculateCartTotal {
const CalculateCartTotal({
required CartRepository cartRepository,
required CouponRepository couponRepository,
required DeliveryRepository deliveryRepository,
}) : _cart = cartRepository,
_coupons = couponRepository,
_delivery = deliveryRepository;
final CartRepository _cart;
final CouponRepository _coupons;
final DeliveryRepository _delivery;
/// Üç mənbəni birləşdirir — buna görə use-case-dir.
Future<CartTotal> call({String? couponCode, required String cityId}) async {
final cart = await _cart.fetchCurrent();
final subtotal = cart.items.fold<double>(
0,
(sum, item) => sum + item.product.finalPrice * item.quantity,
);
// Kupon: yoxdursa, ya da etibarsızdırsa endirim sıfırdır.
final coupon = couponCode == null
? null
: await _coupons.findValid(couponCode, subtotal: subtotal);
final discount = coupon?.discountFor(subtotal) ?? 0;
// Çatdırılma: pulsuz həddi keçildikdə sıfırlanır.
final tariff = await _delivery.tariffFor(cityId);
final shipping =
subtotal - discount >= tariff.freeFrom ? 0.0 : tariff.price;
return CartTotal(
subtotal: subtotal,
discount: discount,
shipping: shipping,
total: subtotal - discount + shipping,
appliedCoupon: coupon,
);
}
}
// ── Riverpod-da bağlanması ──
@riverpod
CalculateCartTotal calculateCartTotal(Ref ref) => CalculateCartTotal(
cartRepository: ref.watch(cartRepositoryProvider),
couponRepository: ref.watch(couponRepositoryProvider),
deliveryRepository: ref.watch(deliveryRepositoryProvider),
);
// ── Notifier sadə qalır: bir çağırış ──
@riverpod
class CartSummary extends _$CartSummary {
@override
Future<CartTotal> build({String? couponCode, required String cityId}) =>
ref.watch(calculateCartTotalProvider)(
couponCode: couponCode,
cityId: cityId,
);
}Şərt №1-ə uyğun real use-case: yekun məbləğ üç ayrı mənbədən asılıdır. Bu məntiqi notifier-də saxlamaq onu test edilməz edərdi.
| Əməliyyat | Şərtlərdən hansı ödənir | Qərar |
|---|---|---|
| "Sifarişlərimi gətir" | Heç biri — bir repository, bir çağırış | Use-case yazma; notifier birbaşa repository-ni çağırsın |
| "Sifarişi ləğv et" | Heç biri | Use-case yazma |
| "Səbətin yekununu hesabla" | №1 (üç repository) + №2 (mürəkkəb qayda) | Use-case yaz |
| "Profili + abunə statusunu göstər" | №1 (iki repository) | Use-case yaz (ya da bir ekran üçünsə notifier-də birləşdir) |
| "Qiyməti valyutaya görə formatla" | Heç biri — bu, göstərmə məntiqidir | Use-case yazma; presentation qatına aiddir |
| "Endirim faizini hesabla" | Heç biri — bir modelin sahələrindən asılıdır | Use-case yazma; domain modelinin getter-i olsun |
| "Ödəniş axını: yoxla, rezerv et, təsdiqlə" | №2 (çox addımlı) + №3 (iki ekran) | Use-case yaz |
Use-case-in forması. Üç variant yayılıb:
- `call()` metodu olan sinif —
calculateTotal(couponCode: ...)kimi çağırılır. Yığcamdır, lakin çağırış yerində metod adı görünmür; naviqasiyada "bu funksiya nədir?" sualı yaranır. - Adlandırılmış metodu olan sinif —
CalculateCartTotal().execute(...)ya daPlaceOrder().placeOrder(...). Daha açıqdır, xüsusilə IDE-də axtarış zamanı. - Sadə funksiya —
Future<CartTotal> calculateCartTotal({required CartRepository cart, ...}). Asılılıqlar parametr olduğu üçün ən sadə testə malikdir, lakin çağırış yerində uzun olur.
Seçim komanda konvensiyasıdır; vacib olan layihə boyu bir formanın olmasıdır.
Adlandırma. Use-case adı fel + isim olur: PlaceOrder, CalculateCartTotal, RefreshSession. OrderUseCase, CartManager, OrderService kimi adlar əməliyyatı gizlədir.
Anti-pattern. Bir repository çağırışını sarımaq: class GetProductsUseCase { Future<List<Product>> call() => _repo.fetchActive(); }. Bu sinif heç nə əlavə etmir — yalnız bir fayl, bir provider, bir mock və bir test faylı artırır. Rəsmi bələdçinin "yalnız lazım olduqda" tövsiyəsi məhz bunu qarşısını almaq üçündür.
Use-case qatı hamısı ya heç biri qərarı deyil. Rəsmi mövqe də bunu təsdiqləyir: use-case-lər tədricən əlavə olunur, və əgər sonda view model-lərin əksəriyyəti onlardan istifadə edirsə, o zaman qalanlarını da köçürmək olar.
Praktik nəticə: lib/domain/usecases/ qovluğunda 3 fayl olmaq tamamilə normaldır — 30 ekranlıq tətbiqdə də. Bu, "yarımçıq arxitektura" deyil, ölçülmüş qərardır.
Praktika. Layihəndə iki repository-nin məlumatını birləşdirən (ya da mürəkkəb hesablama edən) bir yer tap və onu use-case-ə çıxar. Sonra həmin use-case üçün 2 test yaz: uğurlu yol və bir sərhəd halı (məsələn kupon etibarsızdır).
Hazır sayılır: notifier-də yalnız bir çağırış qalıb, testlər fake repository-lərlə işləyir və şəbəkəyə çıxmır.
📚 Mənbələr və sənədlər
- Arxitektura bələdçisi: opsional domain qatırəsmidocs.flutter.dev
Üç şərtin, qazanc/qiymət siyahısının və "yalnız lazım olduqda əlavə et" tövsiyəsinin mənbəyi.
- Arxitektura tövsiyələri: domain qatırəsmidocs.flutter.dev
Domain qatının niyə "conditional" kateqoriyasında olduğu.
- The Clean Architecture (Robert C. Martin)blog.cleancoder.com
"Use Cases" dairəsinin orijinal təsviri — tətbiqə aid biznes qaydaları.