Domain-in təmizliyi: flutter/material import etməmək
Domain qatının təmizliyi bir qaydadan ibarətdir: lib/domain altında Flutter, HTTP və baza paketləri import olunmur. Bu qaydanın üç ölçülə bilən qazancı var.
1. Test sürəti və sadəliyi. Domain testləri widget qaldırmır, pumpWidget və frame gözləmə yoxdur. Bir hesablamanın testi millisaniyələrlə işləyir, ona görə yüzlərlə belə test yazmaq real olur.
2. Daşınabilənlik. Təmiz domain başqa yerdə işləyir: server tərəfi Dart, CLI aləti, ya da ikinci tətbiq (məsələn admin paneli). Domain Colors.red bilirsə, bu mümkün deyil.
3. Flutter versiyasından asılılığın azalması. Flutter API-si dəyişdikdə (widget-lər, tema, Router) domain qatına toxunmaq lazım gəlmir — biznes qaydaları yerində qalır.
Qayda həm də mübahisəni bitirir: "bu import olar, ya olmaz?" sualının cavabı fikir deyil, yoxlama nəticəsidir.
| Import | Domain-də | Səbəb |
|---|---|---|
| `dart:core`, `dart:async`, `dart:math` | ✅ Olar | Dilin özüdür |
| `package:meta`, `package:collection` | ✅ Olar | Təmiz Dart utilitləri, Flutter asılılığı yoxdur |
| `package:freezed_annotation` | ✅ Olar | Təmiz Dart paketidir (`collection`, `json_annotation`, `meta`) |
| `package:flutter/material.dart` | ❌ Olmaz | `Color`, `TimeOfDay`, `BuildContext` — göstərmə qərarları |
| `package:http`, `package:dio` | ❌ Olmaz | Şəbəkə detalıdır; domain "necə" bilməməlidir |
| `package:sqflite`, `package:shared_preferences` | ❌ Olmaz | Saxlama detalıdır və Flutter plugin-idir |
| `package:intl` (formatlaşdırma) | ⚠️ Çəkinmək | Tarix/rəqəm formatı göstərmə məntiqidir — presentation-a aiddir |
| `package:flutter_riverpod` | ❌ Olmaz | Provider-lər presentation/DI qatındadır; domain onları tanımır |
// ══ SIZMA 1: rəng domain modelində ══
// ❌ lib/domain/models/order.dart
Color get statusColor => switch (status) {
OrderStatus.pending => Colors.orange,
OrderStatus.paid => Colors.green,
OrderStatus.cancelled => Colors.red,
OrderStatus.unknown => Colors.grey,
};
// ✅ Domain yalnız statusu verir (artıq `enum` var, əlavə heç nə lazım deyil).
// ✅ Rəng presentation-da, temadan istifadə edərək:
// lib/ui/orders/widgets/order_status_chip.dart
Color _colorFor(OrderStatus status, ColorScheme scheme) => switch (status) {
OrderStatus.pending => scheme.tertiary,
OrderStatus.paid => scheme.primary,
OrderStatus.cancelled => scheme.error,
OrderStatus.unknown => scheme.outline,
};
// Əlavə qazanc: rəng artıq temaya bağlıdır, dark mode özü işləyir.
// ══ SIZMA 2: formatlaşdırma domain modelində ══
// ❌ lib/domain/models/order.dart
import 'package:intl/intl.dart';
String get formattedTotal => NumberFormat.currency(symbol: '₼').format(total);
// ✅ Domain: rəqəm (məna). Presentation: mətn (görünüş).
// lib/domain/models/order.dart
final double total;
// lib/ui/core/formatters/money_format.dart
String formatMoney(double value, Locale locale) =>
NumberFormat.currency(locale: locale.toString(), symbol: '₼')
.format(value);
// Əlavə qazanc: lokalizasiya dəyişəndə domain toxunulmur.
// ══ SIZMA 3: `BuildContext` use-case-də ══
// ❌ lib/domain/usecases/place_order.dart
Future<void> call(BuildContext context, Cart cart) async {
final order = await _repository.place(cart);
Navigator.of(context).pushNamed('/success'); // domain naviqasiya edir
}
// ✅ Use-case nəticə qaytarır, naviqasiya presentation-da qalır.
// lib/domain/usecases/place_order.dart
Future<Order> call(Cart cart) => _repository.place(cart);
// lib/ui/checkout/checkout_notifier.dart → view naviqasiyayı özü edir:
// final order = await ref.read(placeOrderProvider)(cart);
// if (context.mounted) context.go('/orders/${order.id}');Üç tipik sızma və düzəlişi. Hər halda domain məna qaytarır, presentation isə onu görünüşə çevirir.
Qaydayı necə qorumaq. Üç səviyyə var, sadədən güclüyə:
1. Konvensiya — ARCHITECTURE.md-də bir sətir. Ən ucuz, ən zəif: unudulur.
2. CI yoxlaması — grep addımı (əvvəlki mövzuda nümunə verilib). Ucuzdur və pozuntunu build-də tutur.
3. Ayrı paket — domain-i packages/domain/ altında müstəqil Dart paketi kimi saxlamaq. pubspec.yaml-da Flutter asılılığı olmadıqda import 'package:flutter/material.dart' sadəcə həll olunmur — analizator dərhal xəta verir. Bu, ən güclü variantdır, çünki qaydayı dilin özü qoruyur.
Üçüncü variantın qiyməti də var: paket bölgüsü pubspec idarəsi, versiyalaşdırma və (bir neçə paket olduqda) melos kimi alət gətirir. Kiçik layihədə ikinci variant tam kifayətdir.
Boz zonalar. Bəzi hallar açıq deyil və komanda qərarı tələb edir: intl ilə tarix parse etmək (formatlaşdırma deyil, çevirmə — mapper-ə qoymaq daha yaxşıdır), uuid generasiyası (domain-də ola bilər, çünki id biznes anlayışıdır), loqlama (domain-də abstraksiya, implementasiya kənarda).
Praktika. İki addım:
1. Layihəndə grep -rEn "package:(flutter|http|dio|sqflite|intl)" lib/domain işlət. Tapılan hər sətir üçün qərar ver: sızmadır, yoxsa haqlıdır? Sızmaları düzəlt — çox vaxt düzəliş getter-i presentation-a köçürməkdir.
2. Bu yoxlamayı tool/check_layers.sh-a əlavə et və CI addımı kimi işə sal.
Hazır sayılır: skript sıfır kodla çıxır, və lib/domain altındaki testlər flutter_test olmadan (package:test ilə) işləyir.
📚 Mənbələr və sənədlər
- Dart: paketlər yaratmaqrəsmidart.dev
Domain-i ayrı paket kimi ayırmaq üçün — qaydayı analizatorun özü qorusun.
- Flutter: testlərə girişrəsmidocs.flutter.dev
Unit, widget və integration testlərin fərqi — təmiz domain unit test qatını mümkün edir.
- Arxitektura konseptlərirəsmidocs.flutter.dev
Qatların bir-birindən asılı olmamasının rəsmi izahı.