Asılılıq qaydası və dörd dairə
"Clean architecture" termini Robert C. Martin-in 2012-ci ildəki məqaləsindən gəlir. Orada dörd konsentrik dairə var — mərkəzdən kənara:
1. Entities — biznesin özünə aid qaydalar. 2. Use Cases — tətbiqə aid qaydalar (bir ssenari, bir əməliyyat). 3. Interface Adapters — çevirici qat: controller, presenter, repository implementasiyası. 4. Frameworks and Drivers — xarici detallar: UI framework-u, verilənlər bazası, şəbəkə.
Bütün quruluşu bir qayda saxlayır — asılılıq qaydası: mənbə kodundaki asılılıqlar yalnız içəriyə yönələ bilər. Daxili dairə xarici dairə haqqında heç nə bilmir. Yəni entity http paketini, use-case Widget-i, domain modeli sqflite-i import etmir.
Bunun praktik nəticəsi Flutter üçün çox konkretdir: biznes məntiqin Flutter-dən asılı olmur. Onu dart test ilə, cihaz və emulator olmadan yoxlayırsan.
| Dairə | Flutter-də qarşılığı | Nümunə sinif | Nəyi import edə bilər |
|---|---|---|---|
| Entities | Domain modeli | `Product`, `Order`, `Money` | Yalnız `dart:core` və digər domain modelləri |
| Use Cases | Use-case / interactor (opsional qat) | `PlaceOrder`, `LoadCatalog` | Domain modelləri və repository interfeysləri |
| Interface Adapters | Repository implementasiyası, DTO, mapper, notifier | `ProductRepositoryRemote`, `ProductDto` | Domain + service interfeysləri + `json_serializable` |
| Frameworks and Drivers | Widget-lər, `http`/`dio`, `sqflite`, platforma kanalları | `ProductsPage`, `ApiClient`, `DatabaseService` | Hər şeyi — bu, ən kənar dairədir |
Burada məntiqi bir sual yaranır: use-case repository-yə müraciət edir, repository isə şəbəkəyə. Onda asılılıq içəridən kənara getmirmi?
Getmir — çünki arada abstraksiya var. Bu, asılılığın tərsinə çevrilməsi (dependency inversion) adlanır və Dart-da bir sətirlik texnikadır:
- Domain qatında
abstract class ProductRepositoryelan olunur — yalnız metodların imzası. - Data qatında
class ProductRepositoryRemote implements ProductRepositoryyazılır. - Domain data qatını import etmir; data qatı domain-i import edir.
Ox belə çevrilir: kod yazma vaxtı asılılıq içəriyə (data → domain), icra vaxtı isə çağırış kənara (use-case → konkret implementasiya) gedir. Konkret implementasiyanın hansı olduğunu isə üçüncü tərəf — dependency injection — qərar verir.
// ╔══ QAYDAYI POZUR ══════════════════════════════════════╗
// fayl: lib/domain/product.dart
import 'package:http/http.dart'; // ❌ domain şəbəkəni tanıyır
import 'package:flutter/material.dart'; // ❌ domain UI-nı tanıyır
class Product {
Product(this.title, this.priceCents);
final String title;
final int priceCents;
// ❌ domain modeli özü sorğu atır
static Future<Product> fetch(String id) async { /* http.get(...) */ }
// ❌ domain modeli rəng qaytarır — bu, UI qərarıdır
Color get badgeColor => priceCents > 10000 ? Colors.red : Colors.green;
}
// ╔══ QAYDAYI SAXLAYIR ═══════════════════════════════════╗
// fayl: lib/domain/models/product.dart — təmiz Dart, import yoxdur
class Product {
const Product({required this.id, required this.title, required this.price});
final String id;
final String title;
final double price; // artıq manatla: çevirmə mapper-də olub
bool get isExpensive => price > 100; // biznes qaydası — UI deyil
}
// fayl: lib/domain/repositories/product_repository.dart
// Yalnız müqavilə. Domain "necə" olduğunu bilmir, "nə" olduğunu bilir.
abstract class ProductRepository {
Future<List<Product>> fetchActive();
Future<Product> fetchById(String id);
}
// fayl: lib/data/repositories/product_repository_remote.dart
import 'package:my_app/domain/models/product.dart'; // ✅ data → domain
import 'package:my_app/domain/repositories/product_repository.dart';
import 'package:my_app/data/services/product_api_client.dart';
class ProductRepositoryRemote implements ProductRepository {
ProductRepositoryRemote({required ProductApiClient apiClient})
: _apiClient = apiClient;
final ProductApiClient _apiClient;
@override
Future<List<Product>> fetchActive() async {
final dtos = await _apiClient.getActiveProducts();
return dtos.map((dto) => dto.toDomain()).toList();
}
@override
Future<Product> fetchById(String id) async =>
(await _apiClient.getProduct(id)).toDomain();
}Asılılıq qaydası import sətirlərində görünür. Sol tərəf qaydayı pozur, sağ tərəf saxlayır.
Asılılıq qaydasının gözəl tərəfi: o, mexaniki yoxlanıla bilər. Diskussiya lazım deyil — lib/domain qovluğunda flutter/, http, dio, sqflite, shared_preferences sətirlərinin olmaması bir grep addımıdır və CI-də saxlanıla bilər. Belə bir yoxlama komandada "bu import olar, ya olmaz?" mübahisəsini birdəfəlik bitirir.
Praktika. Layihəndə (ya da yeni yaratdığın kiçik nümunədə) lib/domain qovluğu yarat, oraya bir model və bir abstract class repository qoy. Sonra bu əmri işlət:
grep -rE "package:(flutter|http|dio|sqflite)" lib/domain || echo "domain təmizdir"
Hazır sayılır: əmr "domain təmizdir" yazır və repository-nin implementasiyası lib/data altındadır.
📚 Mənbələr və sənədlər
- The Clean Architecture (Robert C. Martin)blog.cleancoder.com
Terminin ilk mənbəyi: dörd dairə və asılılıq qaydası. Flutter-ə aid deyil, ümumi prinsipdir — ona görə oxumaq lazımdır.
- Dart: sinif modifikatorlarırəsmidart.dev
`abstract`, `interface`, `sealed`, `final`, `base` — asılılığın tərsinə çevrilməsi üçün hansı modifikatorun nə verdiyi.
- Arxitektura konseptlərirəsmidocs.flutter.dev
Flutter-in bu prinsipləri hansı dildə ifadə etdiyi — "layers", "separation of concerns", "single source of truth".