Sparround

Niyə arxitektura: 1000 sətrlik widget-in bədəli

Bir ekran sorğu atır, JSON-u açır, qiyməti hesablayır, xətanı göstərir və hamısını bir faylda edir. Bu işləyir — ikinci ekrana eyni məlumat lazım olana qədər.

Problem estetik deyil, tamamilə praktikdir. Belə bir widget-də:

  • Testi yoxdur. Məntiqi yoxlamaq üçün widget-i pumpWidget ilə qaldırmalı və real şəbəkəyə çıxmalısan.
  • Təkrar var. İkinci ekran eyni sorğunu, eyni parse-ı, eyni xəta mesajını yenidən yazır.
  • Dəyişiklik yayılır. API price_cents-i price-a dəyişəndə düzəliş bir yerdə deyil, JSON açarını oxuyan hər faylda edilir.
  • Qərar verə bilmirsən. "Cache əlavə edək" qərarı bir sinifdə deyil, bütün ekranlarda tətbiq olunmalıdır.

Arxitektura bu problemlərin cavabıdır: hansı kodun harada yaşadığı və kimə müraciət edə biləcəyi barədə qaydalar. Qovluqlar bu qaydaların nəticəsidir, məqsədi deyil.

dart
class ProductsPage extends StatefulWidget {
  const ProductsPage({super.key});

  @override
  State<ProductsPage> createState() => _ProductsPageState();
}

class _ProductsPageState extends State<ProductsPage> {
  List<dynamic> _items = [];
  bool _loading = true;
  String? _error;

  @override
  void initState() {
    super.initState();
    _load();
  }

  Future<void> _load() async {
    try {
      // Endpoint və token widget-in içində.
      final response = await http.get(
        Uri.parse('https://api.example.com/v1/products?active=true'),
        headers: {'Authorization': 'Bearer $kToken'},
      );
      if (response.statusCode != 200) {
        setState(() {
          _error = 'Xəta: ${response.statusCode}';
          _loading = false;
        });
        return;
      }
      final decoded = jsonDecode(response.body) as Map<String, dynamic>;
      setState(() {
        _items = decoded['data'] as List<dynamic>;
        _loading = false;
      });
    } catch (e) {
      setState(() {
        _error = e.toString(); // istifadəçi "SocketException" görür
        _loading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_loading) return const Center(child: CircularProgressIndicator());
    if (_error != null) return Center(child: Text(_error!));

    return ListView.builder(
      itemCount: _items.length,
      itemBuilder: (context, i) {
        final raw = _items[i] as Map<String, dynamic>;
        // Biznes qaydası widget-in içində: qəpik → manat.
        final price = (raw['price_cents'] as int) / 100;
        return ListTile(
          title: Text(raw['title'] as String),
          subtitle: Text('$price AZN'),
        );
      },
    );
  }
}

"Hər şey bir yerdə" ekranı. Diqqət et: URL, token, JSON açarları, biznes qaydası (qəpik → manat) və xəta mətni — hamısı widget-in içindədir.

SimptomƏsl səbəbHansı qat həll edir
İkinci ekrana eyni məlumat lazımdır və kod kopyalanırMəlumat yükləmə məntiqi widget-in içindədirRepository — bir dəfə yazılır, hər iki ekran istifadə edir
Testdə real API-yə sorğu gedirWidget HTTP client-i özü yaradırService + DI — testdə fake implementasiya ötürülür
API bir açarı dəyişdi, 6 fayl sındıJSON açarları birbaşa UI-də oxunurDTO + mapper — dəyişiklik bir faylda qalır
Qiymət iki ekranda fərqli formatlanırBiznes qaydası widget-də təkrarlanırDomain modeli — qayda modelin özündə yaşayır
Offline cache əlavə etmək üçün hər ekranı açmaq lazımdırMəlumatın mənbəyi UI-ya bağlıdırRepository — cache qərarı bir yerdə verilir

Diqqət yetir: yuxarıdaki cədvəldə heç bir sətir "kod gözəl görünmür" deyil. Hər sətir ölçülə bilən bir bədəldir — yazılan sətirlərin sayı, sınan faylların sayı, testin işləmə müddəti.

Bu, arxitektura söhbətinin düzgün çərçivəsidir. "Clean architecture" bir moda deyil, konkret suallara cavabdır:

  • Bu məlumatı ikinci dəfə kim istəyəcək?
  • Bu məntiqi şəbəkə olmadan test edə bilirəmmi?
  • Bu dəyişiklik neçə faylı açmağa məcbur edir?

Əks tərəf də var: hər sinif üçün üç qat yazmaq da bədəldir. Bu branch boyu hər qatın qazancını və qiymətini yanaşı göstərəcəyik — sonuncu mərhələdə isə ayrıca "nə vaxt sadələşdirmək lazımdır" mövzusu var.

Praktika. Öz layihəni (ya da tanış bir açıq kod layihəsini) aç, ən böyük widget faylını tap və yuxarıdaki cədvəlin 5 simptomundan hansılarının orada olduğunu bir-bir yaz. Hər simptom üçün bir sətir: "simptom → hansı qat həll edərdi". Hazır sayılır: əlində ən azı 3 sətirlik siyahı var və hər sətirdə konkret fayl adı göstərilib.

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

  • Arxitektura: girişrəsmidocs.flutter.dev

    Flutter komandasının arxitektura bölməsinin başlanğıc səhifəsi — burada bütün alt səhifələrin xəritəsi var.

  • Arxitektura konseptlərirəsmidocs.flutter.dev

    Separation of concerns, qatlar və vahid həqiqət mənbəyi kimi terminlərin rəsmi izahı.

  • Arxitektura tövsiyələrirəsmidocs.flutter.dev

    "Data və UI qatlarını aydın ayırın" tövsiyəsi burada, ən yüksək prioritetlə (strongly recommend) verilib.