Flutter Dev Talk
133 subscribers
17 photos
2 videos
2 files
42 links
Flutter bo'yicha kuchli networking, yangilikar va tajriba almashish zonasini tashkil qilish;
Download Telegram
#ARCH-2 Over-engineering, YAGNI & KISS tushunchalari


Arxitekturaning maqsadi ko'p layer qo'shish emas balki o'zgarishlarni cost'ni boshqarish dedik.

Buyerda ikkita extrimum bor:

Under-engineering Over-engineering

hamma logic har narsaga
bitta joyda abstraction
│ │
└─────────── BALANCE ──────────────┘
kerakli darajada
architecture


Maqsad hozirgi requirementlarga mos sodda yechim qilish va shu bilan birgalikda keyingi o'zgarishlarga ham yo'l ochib qo'yish kerak.

2.1: YAGNI - You Aren't Gonna Need It.

Requirement:
Hozir bizda faqat Paynet bilan to'lov qilinadi.


Lekin biz buning keyinchalik qo'shilishi mumkin deb quyidagicha yozishimiz mumkin :

abstract interface class PaymentStrategy {
Future<void> pay();
}

class PaynetStrategy implements PaymentStrategy {}
class ClickStrategy implements PaymentStrategy {}
class PaymeStrategy implements PaymentStrategy {}

Biz hali bilmagan flowlar uchun taxminan ko'd yozdik.

YAGNI esa shunday deydi:
Hozir kerak bo‘lmagan capability’ni faqat kelajakda balki kerak bo‘lishi mumkinligi uchun yaratma.

2.2: KISS - Keep It Simple.
Tepadagi requirement bo'yicha davom etsak:

class PaymentStrategy {
final PaymentApi api;

PaymentStrategy(this.api);

Future<void> create() {
/// Payment amalga oshirilmoqda
}

Future<void> confirm() {
/// Payment amalga oshirilmoqda
}

Future<void> payment() {
/// Payment amalga oshirilmoqda
}
}

va bu yerda

Bu yerda usecase yoki repository ishlatmaslik bu ko'dni yomon qilmaydi. Lekin keyinchalik buyerga boshqa to'lov providerlari qo'shilsa va ular har xil flowlarga ega bo'lsa ko'd if/else dan iborat bo'lib qoladi.

if (type == paynet) {
// create
// confirm
// payment
} else if (type == click) {
// amount check
// confirm
// payment
} else if (type == payme) {
// confirm
// create
// extra validation
// payment
}


Endi bu yerda KISS-ku hammasini bir joyda saqlashimiz kerak deyish xato. Real complexity paydo bo'lganda abstractionni ishlatishga sabab paydo bo'ladi.

abstract interface class PaymentFlow {
Future<void> execute(PaymentRequest request);
}

class PaynetFlow implements PaymentFlow { ... }

class ClickFlow implements PaymentFlow { ... }

class PaymeFlow implements PaymentFlow { ... }

Bu abstraction kelajakdgi taxminlarga emas balki mavjud ko'dni boshqarish uchun yozildi.

2.3: Preparatory refacoring
2.2-qismdagi refactoringni birdaniga amalga oshirish risk xisoblanadi. Shuning uchun avval mavud flowni refacoring qilamiz va uni testlaymiz. Test yiqilsa demak refactoringda xatolik bo'ladi. Testlardan o'tgach qo'shimcha qismlarni ham qo'shamiz va testlaymiz, agar test yiqilsa qo'shimcha kiritilgan qismdan xatolikni izlaymiz.
Refactor qilish uchun qonunyat shunday desak bo'ladi(lekin qat'iy emas):

1-qadam → implement qiling

2-qadam → similarity'ni kuzating

3-qadam → pattern aniq bo‘lsa abstraction haqida o‘ylang


Xulosa:
Aniq responsibilityga ega bo'lmagan layerlarni ko'paytirish - over-engineeringga, ularni bir joyga jamlash esa under-engineering hisoblanadi. Senior vazifasi shu ikki tomonni balanslash.


Architecture decision = current complexity + change probability + cost of failure


@flutterDevTalk
👍1🔥1
#ARCH-3 MVA - Minimum Viable Architecture


MVA - eng kam faylli arxitektura emas balki productni hozirgi holati uchun ishlashga yetarli sodda arxitektura xisoblanadi. Kechagi mavzu YAGNI va KISS tushunchalarni davomi sifatida ushbu tushuncha ham hali yo'q complexity uchun abstraction qilishdan qaytaradi. Arxitektura xam product o'sgan sari u bilan birga o'sa oladigan bo'lishi kerak. Va kerakli paytda responsibilitylarga boundry qo'ya olish kerak.

Junior layerlarni clean arxitekturada ko'rgani uchun qo'shish kerak desa, Senior requirementlarga qarab arxitektura tanlaydi.

Bu folderlarni borligi proekt yaxshi arxitekturalanganini isbotlamaydi:

domain/
data/
presentation/


Bu kabi sodda ko'rinish ham hozirgi requirement uchun yaxshi arxitektura bo'lishi mumkin:

features/payment/
payment_bloc.dart
payment_service.dart
payment_api.dart



current needs
│
too little ─────── MVA ─────── too much
architecture architecture


Arxitektura - bir martta tanlab, muzlatib qo'yiladigan narsa emas balki product bilan birga rivojlanadigan bo'lishi kerak.


@flutterDevTalk
🔥1
#fun

Senior: Buglarni qanday to'g'irlading ?
Junior: - Ko'dlarni o'chirvordim 😏.

vs

Junior: Buglarni qanday to'g'irlading ?
Senior: - Sen yozgan ko'dlarni commentaryaga oldim 😂😂.
Please open Telegram to view this post
VIEW IN TELEGRAM
😁6
#ARCH-4 DIP - Dependency Inversion Principle


4.1: SOLID ning D tamoyili yani Dependency Inversion (o'zbekcha aytganda teskari bog'liqlik) nima ? Nima uchun ishlatiladi ? Nima foydasi bor ?

Dependency Inversion - soddaroq aytganda:
Biznes logika texnik detallarni bilmasligi kerak.


E aka bu ham bo'lganku, sal soddalashtiringee desangiz hop unda yanada soddalashtiraman.

Quyidagicha flow bor:

Transfer → Confirmation → Bill → Payment

va buni biz oddiygina Payment bloc ichida yozib qo'ya qolsak bo'ladi:

class PaymentBloc {
final Dio dio = Dio();

Future<void> pay() async {
await dio.post('/payment');
}
}


Lekin bloc bu holatda 2 narsadan habardor bo'lib qoladi: PaymentBloc biznes flowni va http request qanday ketayotganini. Mobodo ertaga api o'zgarsa, qo'shimcha validatsiya qo'shilsa yoki test yozmoqchi bo'lsak biznes qismga tegishga to'g'ri keladi.

DIP ni qo'llamasdan oldin dependency yo'nalish quydagicha edi:

PaymentBloc
↓
Dio
↓
Backend

DIP bilan esa shu ko'rinishga keladi:

PaymentBloc
↓
PaymentRepository ← abstraction
↑
PaymentRepositoryImpl
↓
Dio
↓
Backend

va PaymentBloc esa ushbu holatga keladi:

class PaymentBloc {
final PaymentRepository repository;

PaymentBloc(this.repository);

Future<void> pay() async {
await repository.pay();
}
}

quyi qismlarni o'zilar tushunvoldilar degan umiddaman.

Hop endi nima o'zgardi ?. Endi PaymentBloc hechnimadan habari yoq shunchaki menga pay qilish uchun nimadir berilar deydi holos. Va agar endi api o'zgarsa yoki boshqa service qilinsa PaymentBloc ga umman qiziq emas u faqat pay uchun service kutadi holos. o'zgarishlar esa pastki texnik qisimlarda yuz beradi.
Manashu yerda Dependency Inversion'ning manosi chiqadi. Ya'ni texnik o'zgarishlar biznes qoidalarga moslashishi kerak, biznes qoidalar texnik o'zgarishlarga emas.

4.2: Endi esa DIP bilan DI ni farqini aniqlashtirvolamiz.
Dependency Inversion - prinsip. Dependency Injection - texnika


DIP - Dependency qaysi tomonga qarashi kerakli haqidagi arxitekturaviy qaror.
DI - Dependency objectni klassga qanday yetkazish usuli.

Quyidagi ikki usul ham Dependency Injection hisoblanadi:

PaymentBloc(
PaymentRepositoryImpl(Dio()),
);

get_it bilan

getIt.registerLazySingleton<PaymentRepository>(
() => PaymentRepositoryImpl(getIt<Dio>()),
);

getIt.registerFactory(
() => PaymentBloc(getIt<PaymentRepository>()),
);


Xulos: Senior sifatida arxitektura qurayotkanda dependency yo'nalishlarni to'g'ri qurish keyinchalik ko'dlarni o'zgarish chegaralari boshqarish va oson testlashga yordam beradi.
#ARCH-5 IoC - Inversion of Control & Dependency Injection


Oldingi postda DIP nima va uni DI dan farqi haqida gaplashgandik, hozir esa sizlar bilan IoC va DI bog'liqligini o'rganamiz.

IoC ni tushuntirish uchun ushbu ko'dga e'tibor beramiz:

class PaymentBloc extends Bloc<PaymentEvent, PaymentState> {
final repository = PaymentRepositoryImpl(
PaymentApi(Dio()),
);
}

Buyerda PaymentBloc obektlarni va dependencylarni yaratyapti. Sizningcha shu ko'd qismi normalmi ?
IoC uchun esa bu normal emas u shunday deydi :
"Hoy class ichidagi dependencylar o'z obektini o'zi yaratmasin, ularni tashqarida yaratib chaqirsin" deydi.

Ya'ni bunday:

class PaymentBloc extends Bloc<PaymentEvent, PaymentState> {
final PaymentRepository repository;
PaymentBloc(this.repository);
}

va endi PaymentBloc hechnimadan habari yoq faqat PaymentRepo kutadi holos. Barcha texnik ishlar quyi qismda bajariladi.

Endi IoC vs DIP vs DI bular nimaligini aniqlashtirvolsak:

DIP
│
│ architecture/design principle
│ dependency qaysi tomonga qaraydi?
↓
Business → Abstraction ← Infrastructure


IoC
│
│ control kimda?
↓
Object creation/lifecycle tashqariga chiqadi


DI
│
│ dependency qanday beriladi?
↓
Constructor / setter / parameter orqali


Hop bu nima uchun kerak desangiz - bu oldingi postlarda aytganimdek changelarni radiusini kichiraytirish uchun kerak bo'ladi. Undan tashqari bu ko'dlarning testabilityligini oshiradi:

PaymentRepository
↑ ↑
│ │
Real Fake
│ │
Production Test


Xulosa: Ushbu qoidalarga amal qilish ko'd arxitekturasini o'zgarishlarga moslashuvchan, tushunarli va testlash uchun oson qiladi.


@flutterDevTalk
Arxitektura bo'yicha overviewlar kimgadir foydali bo'lyaptimi 👀 , davom etaylikmi ?
👍9👎2
#ARCH-6 One Destination, Many Roads


Bugun sizlar bilan turli xil patternlar bilan ko'zlangan arxitektural maqsadga borishni o'rganamiz.
1. Arxitektura - maqsad emas balki bosita. Yani biz o'zimizcha "Clean Architecture qilamiz", "MVVM qilamiz" yoki "Repository qo'shamiz" deya design qarorini qila olmaymiz. Birinchi navbatda 3 savol: Why(Nega)->How(Qanday)->What(Nima) savollariga javob berishimiz kerak.

WHY
Nimani himoya qilmoqchimiz?
↓
HOW
Qanday boundary/data-flow kerak?
↓
WHAT
BLoC? MVVM? Repository? UseCase?

Masalan payment shunday:

Transfer → Confirmation → Bill → Payment

Bu uchun quyidagicha sodda design ham yetishi mumkin:

PaymentPage
↓
PaymentBloc
↓
PaymentService
↓
API

Clean Arxitektura'da bunday emasku deb
Reposiyoty
,
UseCase
qo'shish shart emas. Bu kichik team, sodda flow va kam business logic uchun yetarli MVA(Minimum Viable Architecture) hisoblanadi.

Masalan shu product o'sdi deylik. O'rtada komissya hisoblash, har xil validatsiyalar, retry va local providerlar qo'shildi deylik shunda business logicni UI va infrastruktura o'zgarishidan himoya qilish boshqacha yechimni talab qilishi mumkin:

Presentation
PaymentBloc
↓
Application/Domain
PayBill
↓
PaymentRepository
↓
Infrastructure
API + Cache + Provider SDK

Muhum nuqta: UseCaseni qo'shish bilan arxitekturani yaxshilanmaydi. Uni haqiqiy responsibility qo'shilganda qo'shish kerak.

Bir destination, ikki road:
Requirement:
```
UI backend implementatsiyasini bilmasin, busines flow testable bo'lsin.
```
Team A:

Widget
↓
BLoC
↓
UseCase
↓
Repository

Team B:

Widget
↓
ViewModel
↓
Domain Service
↓
Gateway

Nomlar boshqacha, data flowlar ham farq qilishi mumkin lekin buyerda beriladigan savol:
Qaysi pattern ?
balki:
Business logic qayerda ?
Dependency qaysi tomonga qarayapti ?
Infrastrukturani o'zgartirsa nima buziladi ?
Feature'ni tushunish osonmi ?
Test qilish qanchalik oson ?
Change cost qancha ?


Chunki :
BLoC ≠ architecture
MVVM ≠ architecture
Repository ≠ architecture
Clean Architecture ≠ papkalar strukturasi.

Bular shunchaki architectural objectivga erishish uchun ishlatiladigan design choice holos.

Xulosa: Arxitektura bu oddiy tanlov emas, product context va requirementlar arxitekturani tanlaydi. Keraksiz requirementlar uchun to'nnalab ko'd yozib o'zingizni qiynamang. To'g'ri arxitektura tanlashni shuyerda o'rganing 😁


@flutterDevTalk
Endi esa o'zim bilan bo'lgan tajriba va xatolarni bo'lishaman.
Boshida UIC groupda ishlaganimda TDD(aniqroq aytganda testsiz DD 😅, hozir o'ylasam faqatgina papka strukturasidan foydalangan ekanmiz🤣) arxitekturasi bo'yicha ko'd yozardik. Boshida Entity va Model farqini tushunishimga ancha vaqt ketgan, keyinchalik esa UseCase va Repository nima uchun kerakligini tushunishga yana ko'p vaqtimni ketkazdim. Hammasini shunchaki borligi uchun, "Clean Arxitektura"da bo'lishi kerak bo'lgani uchun yozganman. Hozir axitektura bo'yicha o'qiyotgan kitobni o'sha paytda olganman va o'qib hechnimani tushunmagan edim. Hozir yig'ilgan tajribalar, olingan bilimlar bilan birgalikda o'qisam nimalarnidir tushunyapman 😅.

TDD bo'yicha folder strukturamiz asosan shunday edi:
lib/
data/
domain/
presentation/
Va har doim shunday yozardik
presentation → usecase → repository -> datasource
Lekin deyarli hamma holatda usecase'ni vazifasi shunchaki method forward edi holos:
Future<Product> call(String id) {
return repository.getProduct(id);
}
1. Proektda nechta api bo'lsa shuncha usecase yozib chiqilardi, bularni barchasi shunchaki bor bo'lishi kerakligi uchun yozilgan edi, qandaydir change bo'lsa ko'plab qismni o'zgartirib chiqishga to'g'ri kelar edi. Hozir tushunib yettimki usecase qo'shish ko'dni clean qilmaydi. Why->How->What savollarini berish ancha o'rinli bo'lar ekan.
2. Classga UseCase so'zini qo'shish bilan u usecasega aylanib qolmaydi. Qachonki unga real extiyoj tug'ilgandagina qo'shish o'rinli bo'ladi.
3. BLoC barcha logicani qilishi responsibility buzilishi hisoblanadi. Bloc'ni asosiy vazigasi UI dan eventlarni qabul qilish va statelarni boshqarish. Quyidagicha yozish xato hisoblanadi:
on<PayPressed>((event, emit) async {
emit(PaymentLoading());
final bill = await billRepository.getBill(event.billId);
if (bill.isExpired) {
emit(PaymentFailure('Bill expired'));
return;
}
final limit = await limitRepository.getLimit();
if (bill.amount > limit) {
emit(PaymentFailure('Limit exceeded'));
return;
}
if (bill.currency != 'UZS') {
final rate = await currencyRepository.getRate(bill.currency);
}
final commission = bill.amount * 0.01;
final result = await paymentRepository.pay(
bill,
commission,
);
emit(PaymentSuccess(result));
});
Buyerda Bloc ikkita vazifani bajarmoqda:
PaymentBloc
├── UI state management ✅
│ ├── Loading
│ ├── Success
│ └── Failure
│
└── Business/Application logic ⚠️
├── bill expired?
├── limit check
├── currency conversion
├── commission
└── payment orchestration
Usecase ayana manashu qismda o'rinli bo'ladi:
class PayBillUseCase {
PayBillUseCase(
this.billRepository,
this.limitRepository,
this.paymentRepository,
);
final BillRepository billRepository;
final LimitRepository limitRepository;
final PaymentRepository paymentRepository;
Future<PaymentResult> call(String billId) async {
final bill = await billRepository.getBill(billId);
if (bill.isExpired) {
throw BillExpiredException();
}
final limit = await limitRepository.getLimit();
if (bill.amount > limit) {
throw LimitExceededException();
}
return paymentRepository.pay(bill);
}
}
Bloc esa endi faqatgina statega javob beradi:
on<PayPressed>((event, emit) async {
emit(PaymentLoading());
try {
final result = await payBill(event.billId);
emit(PaymentSuccess(result));
} on BillExpiredException {
emit(PaymentFailure('Bill expired'));
} on LimitExceededException {
emit(PaymentFailure('Limit exceeded'));
}
});
Chegaralar endi aniqlashdi:
UI
↓
Bloc
│
│ "Pay bosildi"
│ "Loading ko‘rsat"
│ "Success/Failure ko‘rsat"
↓
PayBillUseCase
│
│ "Payment qanday bajariladi?"
│
├── BillRepository
├── LimitRepository
└── PaymentRepository
Ko'pchilik qilishi mumkin bo'lgan xato (menda ko'p bo'lgan):
if business logica hisoblanmaydi. Masalan UI dagi ushbu qism to'liq UI behavior hisoblanadi:
if (state is PaymentLoading) {
return const CircularProgressIndicator();
}
lekin
if (transfer.amount > dailyLimit) ...
if (bill.isExpired) ...
if (!recipient.canReceiveTransfer) ...
commission = amount * tariff.rate;
kabi business/domain/data to'g'ridan to'g'ri ishlatilsa xato hisoblanadi.

Shuning uchun UI yoki BLoC da if bo'lmasligi kerak degan tushuncha yoq faqat savol:
Bu ui qanday ishlashiga tegishlimi yoki business qanday ishlashigami ?

Xulosa:
BLoC presentation layerga tegishli bo‘lgani uchun uning asosiy vazifasi eventlarni qabul qilish va UI state’larni boshqarish. Oddiy applicationlarda ozgina orchestration BLoC’da bo‘lishi normal va alohida UseCase yaratish over-engineering bo‘lishi mumkin. Lekin business rules va murakkab application flow BLoC ichida ko‘payib borsa, ularni domain entity yoki UseCase’ga ajratish kerak. Shunda business logic Flutter/BLoC’dan mustaqil, testable va reusable bo‘ladi.

@flutterDevTalk
👍5
💬 Let's talk:

Endi esa mikrafon sizlarda: O'zilar ishlatyotgan arxitektura qanday ? Nimalari yoqadi va nimalar keraksiz deb o'ylaysiz ? Oldin qanday edi hozir esa nimalarni tushundingiz ?
Please open Telegram to view this post
VIEW IN TELEGRAM
Flutter Dev Talk pinned «💬 Let's talk: Endi esa mikrafon sizlarda: O'zilar ishlatyotgan arxitektura qanday ? Nimalari yoqadi va nimalar keraksiz deb o'ylaysiz ? Oldin qanday edi hozir esa nimalarni tushundingiz ?»
#ARCH-7 MVC, MVP, MVVM, MVI — farqi nimada?


Ko'pchilik eshitadigan lekin ko'pincha farqini bilmaydiganlar patternlar, bugungi post ularni tushunish uchun ancha foydali bo'ladi degan umiddamn. Bu patternlarning asosiy maqsadi bir xil: UI va logic orasidagi mas’uliyatni to‘g‘ri ajratish. Farqi — ular qanday aloqa qilishida.

MVC — Model, View, Controller

View → Controller → Model

Controller user actionlarini qabul qiladi va Model bilan ishlaydi. Oddiy loyihalarda tushunarli, lekin murakkab UI’da Controller kattalashib ketishi mumkin.

MVP — Model, View, Presenter

View → Presenter → Model
↓
View

Presenter logicni boshqaradi va View’ga showLoading(), showError() kabi buyruqlar beradi. Kamchiligi — Presenter View bilan kuchliroq bog‘lanishi mumkin.

MVVM — Model, View, ViewModel

View → ViewModel
↓
State
↓
View

ViewModel View’ga nima qilishni buyurmaydi. U state beradi, View esa state’ni kuzatadi.

Flutter’da Cubit/BLoC + state bilan ko‘p hollarda shunga yaqin yondashuv quriladi.

MVI — Model, View, Intent

Intent → Logic → State → View
↑ │
└─────────────────────┘

Asosiy g‘oya — Unidirectional Data Flow. Event keladi → yangi state hosil bo‘ladi → UI state asosida render qilinadi.

Masalan:


PayPressed
↓
Processing
↓
Success / Failure
↓
UI


💡 Muhim: BLoC ishlatish avtomatik ravishda MVI yoki Clean Architecture degani emas. Patternni package emas, responsibility va data flow belgilaydi.

Qisqacha:


MVC → Controller boshqaradi
MVP → Presenter View'ga buyruq beradi
MVVM → View state'ni kuzatadi
MVI → Intent → State → View oqimi


Xulosa: Pattern tanlashda “qaysi biri eng zo‘r?” emas, loyiha murakkabligi uchun qaysi biri yetarli? degan savol muhim.


@flutterDevTalk
👍2❤1
#ARCH-8 Logic & Validations — qaysi logic qayerda turadi?


Architecture’da ko‘p uchraydigan savol:
“Bu `if` qaysi layer’da turishi kerak?”
Buni `if`ning o‘ziga emas, u qanday qaror chiqarayotganiga qarab aniqlaymiz.

📱 1. Presentation — UI qanday behave qiladi?

if (state.isLoading) {
// button disabled
}

Loading, button state, error ko‘rsatish — presentation responsibility.

💼 2. Domain — business bo‘yicha mumkinmi?
Masalan transfer uchun:

bool canTransfer({
required int amount,
required int min,
required int max,
}) {
return amount >= min && amount <= max;
}

amount limit ichidami — business rule.

⚙️ 3. Application / UseCase — operation qanday bajariladi?

limitni olish
↓
balance'ni olish
↓
business rule'ni tekshirish
↓
transferni boshlash

UseCase bir nechta business/data operatsiyalarini orchestrate qilishi mumkin.

🌐 4. Data — ma’lumot qayerdan va qanday keladi?

API → DTO → mapping → domain model

HTTP request, JSON parsing, DTO mapping — data concern.

Masalan:

API'dan min/max olish → Data
amount limit ichidami? → Domain
transfer jarayonini boshqarish → Application
Pay buttonni disable qilish → Presentation


💡 Xulosa:
> “Flutter/UI’ni olib tashlasam, bu qoida hali ham ma’noga egami?”
Agar ha bo‘lsa, u katta ehtimol bilan business/domain logic.
Agar “UI qanday ko‘rinadi?” degan savolga javob bersa — presentation logic. Va har bir validation uchun UseCase, Service, Validator yaratish shart emas.
🎯 Architecture — ko‘proq layer yaratish emas. Logic uchun to‘g‘ri owner topish.

@flutterDevTalk
🔥1
💻 Hohlovchilar bo'lsa dam olish kunlarida LiveCoding qilib o'zimning eski ko'dlarimdan bir qismini olib yoki oramizda ko'dini review qildirmoqchi bo'lganlarni ko'dini review qilishimiz mumkin nima deysizlar ?

🚩 P.s/ Ko'd review uchun @iamtechie ga yozsangiz gaplashamiz.
Please open Telegram to view this post
VIEW IN TELEGRAM
👏9❤2🔥2
#ARCH-9 Validation Boundary

Dasturlarda ko'p uchraydigan savol qaysi logicani qayerda tekshirishim kerak, buni validation misolida ko'rib chiqamiz.

Validation’da ko‘p uchraydigan savol:

- “Buni qaysi layer tekshirishi kerak?”

Yaxshiroq savol esa:

- “Bu qiymatga qaysi boundary ishonishi mumkin?”

Masalan, transfer summasi:

User input: "6000000"


Bir xil amount bir necha joyda tekshirilishi mumkin:

1️⃣ Presentation / Input

final amount = int.tryParse(input);

Savol: “Bu input umuman valid raqammi?”

2️⃣ Domain / Business

if (amount < min || amount > max) {
return AmountNotAllowed();
}

Savol: “Business bo‘yicha bu summaga ruxsat bormi?”

3️⃣ Server
Server ham limitni qayta tekshiradi.
Savol: “Hozirgi authoritative ma’lumot bo‘yicha transaction haqiqatan ruxsat etilganmi?”

Flow:


"6000000"
↓
Presentation
parse bo‘ladimi?
↓
Domain
business rule'dan o‘tadimi?
↓
Server
haqiqatan ruxsatmi?

Bu har doim duplicate validation emas.

Har bir boundary boshqa narsaga javobgar:


Presentation → yaxshi UX va valid input
Domain → business correctness
Server → authoritative validation


Masalan, client max = 5 000 000 deb tekshirganidan keyin serverdagi limit `4 000 000`ga o‘zgarishi mumkin.
Shuning uchun:

> Client validation server validation’ni almashtirmaydi.

💡 Architecture’da validation’ni joylashtirishda faqat “kim tekshiradi?” deb emas, “bu layer nimaga ishonishi mumkin va qanday qaror uchun javobgar?” deb o‘ylash foydaliroq.

🎯 Bir xil data bir nechta boundary’da tekshirilishi mumkin, chunki har bir boundary’ning responsibility’si boshqa.
❤1🔥1
#ARCH-10 Architecture Learning — DTO, Domain Model & Mapper


Bugun hammaga qiziq bo'lgan va o'zim farqiga borishga juda qiynalgan mavzuni ko'rib chiqamiz. Bundan keyin Model va Entityni farqi nimaga ishlatish va qachon ishlatish kerak ekanini bilib olamiz.

⚠️ Buyerda DTO biz bilgan Model sifatida, Domain Model esa Entity sifatida gapirilgan.

API’dan kelgan modelni to‘g‘ridan-to‘g‘ri butun app’da ishlatish har doim ham yaxshi yechim emas.

Masalan backend:

{
"article_id": 123,
"liked": 1,
"status": "PUBLISHED"
}

qaytardi.

Bu — DTO (Data Transfer Object):

class ArticleDto {
final int articleId;
final int liked;
final String status;
}

DTO - “Server data’ni qanday ifodalaydi?” savolga javob beradi.

Application ichida esa bizga qulayroq model kerak bo‘lishi mumkin:

class Article {
final int id;
final bool isLiked;
final ArticleStatus status;
}

Bu — Domain Model(yoki Entity).

Ular orasida Mapper ishlaydi:

Article toDomain(ArticleDto dto) {
return Article(
id: dto.articleId,
isLiked: dto.liked == 1,
status: mapStatus(dto.status),
);
}


Flow:

API
↓
DTO
↓
Mapper
↓
Domain Model
↓
Application / UI


💡 Asosiy foyda — backend contract o‘zgarishini application ichiga tarqatmaslik.

Masalan:

liked: 1


ertaga:

is_liked: true

bo‘lsa, ideal holatda o‘zgarish DTO + Mapper atrofida qoladi.

• Anemic vs Rich Model

Domain Model faqat data saqlasa:

article.isLiked
article.status

→ Anemic Model

O‘ziga tegishli behaviorni ham bilsa:

bool get canLike =>
status == ArticleStatus.published && !isLiked;

→ Rich Model hisoblanadi.

Lekin Rich Model har doim yaxshiroq degani emas.

Xar doimgi qoidamiz: DTO → Domain → UI uchun alohida model yaratishning o‘zi Clean Architecture emas.
Agar API va application modeli bir xil va sodda bo‘lsa — bitta model yetishi mumkin.

Agar backend contract tez o‘zgarsa, representation farq qilsa, bir nechta data source bo‘lsa yoki business model tashqi datadan mustaqil bo‘lishi kerak bo‘lsa — DTO + Mapper + Domain Model ajratilishi real qiymat beradi.

🎯 Senior qarori: Abstraction’ni layer ko‘paytirish uchun emas, o‘zgarishning tarqalishini to‘xtatish uchun yaratiladi.

@flutterDevTalk
Please open Telegram to view this post
VIEW IN TELEGRAM
#ARCH-11 Architecture Learning — Mapper, Domain Invariant & Business Rule


Payment flow’da API’dan kelgan ma’lumotni Domain Model’ga aylantirish faqat fieldlarni ko‘chirish emas.

Muhim savol:
Tashqi tizimdan kelgan ma’lumotga qachondan boshlab ishonishimiz mumkin?

Masalan, Payment API quyidagicha response qaytardi:

{
"bill_id": "12345",
"amount": -100000,
"commission": 1500,
"status": "CREATED"
}

Bu yerda uchta tushunchani ajratishimiz kerak.

1️⃣ Mapper — Data’ni tarjima qilish

PaymentStatus mapStatus(String status) {
return switch (status) {
'CREATED' => PaymentStatus.created,
'PAID' => PaymentStatus.paid,
'EXPIRED' => PaymentStatus.expired,
_ => throw FormatException('Unknown status'),
};
}

Mapper API representation’ni application tushunadigan formatga aylantiradi.

2️⃣ Domain Invariant — Model doim valid bo‘lishi kerak

class Payment {
Payment({
required this.amount,
required this.commission,
}) {
if (amount <= 0 || commission < 0) {
throw ArgumentError('Invalid payment');
}
}
// Summalar eng kichik pul birligida
final int amount;
final int commission;
int get total => amount + commission;
}


Invariant — model mavjud bo‘lishi uchun bajarilishi shart bo‘lgan qoida.
Masalan, bizning taxminiy talabimizda:

- amount > 0
- commission >= 0
-

Bu shartlar buzilsa, valid Payment yaratib bo‘lmaydi.

3️⃣ Business Rule — Payment qilish mumkinmi?

bool canPay({
required Payment payment,
required int balance,
}) {
return balance >= payment.total;
}

Payment modeli valid bo‘lishi uning to‘lanishi mumkinligini anglatmaydi.

Masalan:

Amount: 100 000
Commission: 1 500
Total: 101 500
Balance: 100 000


Payment valid, lekin balans yetarli emas.

Flow:


Payment API
↓
PaymentDto
↓
Mapper → API data validmi?
↓
Payment Model → Invariant bajarilganmi?
↓
Business Rule → Payment qilish mumkinmi?
↓
Application → Payment flow


💡 Senior yondashuvi

Mapper tashqi data’ni tarjima qiladi. Domain Model o‘z invariantlarini himoya qiladi. Business Rule esa operation bajarilishiga ruxsat borligini aniqlaydi.

Biroq client’dagi canPay tekshiruvi serverning yakuniy validation’ini almashtirmaydi. Balans yoki payment status tekshiruvdan keyin o‘zgarishi mumkin.

🎯 Asosiy xulosa:

Valid Payment ≠ Payable Payment

Model valid bo‘lishi boshqa, shu model bilan payment operation bajarishga ruxsat bo‘lishi boshqa masala.

Architecture’da muhim narsa har bir `if`ni alohida layer’ga chiqarish emas, balki har bir qoida uchun to‘g‘ri owner tanlashdir.

@flutterDevTalk
Uzum ertalabdan beri ishlamayapti. Bunga sabab yandex serverlari dron hujumiga uchragani va uzum yandex serverlaridan foydalangani deyishyapti.

Shu to’g’rimikin ??
#ARCH-12 Architecture Learning — Soft vs Hard Boundary


Flutter’da kodlarni bir-biridan ajratishning ikki xil usuli bor.

1️⃣ Soft Boundary — Yumshoq chegara

Kodlarni papkalarga ajratamiz:


lib/
├── presentation/
├── domain/
└── data/


Lekin presentation ichidan `data`ni to‘g‘ridan-to‘g‘ri import qilish mumkin.

Ya’ni chegara bor, lekin uni buzishga texnik to‘siq yo‘q.

2️⃣ Hard Boundary — Qattiq chegara

Kodlarni alohida package’larga ajratamiz:


packages/
├── presentation/
├── domain/
└── data/


Masalan, presentation faqat `domain`ga dependency qilsa, `data`ni to‘g‘ridan-to‘g‘ri import qila olmaydi.

Ya’ni chegara texnik jihatdan ham nazorat qilinadi.

💡 Qaysi biri yaxshiroq?

Kichik loyihalarda Soft Boundary yetarli bo‘lishi mumkin. Katta loyihalarda esa Hard Boundary dependency’larni nazorat qilishga yordam beradi.

Soft Boundary — developer qoidalarga amal qiladi.

Hard Boundary — texnik cheklovlar ham qoidalarga amal qilishga majbur qiladi.

Xulosa: Yaxshi arxitektura — ko‘p package yaratish emas, kerakli joyda to‘g‘ri chegara qo‘yish.


@flutterDevTalk
#ARCH-13 Architecture Learning — Soft vs Hard Boundary (PART 2)


📌 Shared Module — kodni qayta ishlatish har doim ham yaxshi yechimmi?

Flutter’da bir nechta feature’da bir xil kod uchrasa, uni shared modulga chiqarishimiz mumkin.

Bu kod takrorlanishini kamaytiradi. Lekin ba’zida ortiqcha bog‘liqlik ham yaratadi.

💻 Oddiy misol:

Tasavvur qilaylik, ilovamizda ikkita feature bor:
- payment — to‘lov qilish
- transfer — pul o‘tkazish

Ikkalasida ham Limit modeli mavjud:


class Limit {
final int minAmount;
final int maxAmount;
}


Kodlar bir xil ekan deb, bitta shared/Limit modelini yaratdik.

Endi ikkala feature ham shu modelga bog‘liq.

⚠️ Muammo qayerda?

Ertaga transfer uchun qo‘shimcha limit qoidasi kerak bo‘lib qolsa, umumiy modelni o‘zgartirishimiz mumkin.

Bu esa `payment`ga ham ta’sir qilishi yoki model ichida keraksiz shartlar paydo bo‘lishiga olib kelishi mumkin.

Ya’ni kod takrorlanishini kamaytirdik, lekin feature’larni bir-biriga bog‘lab qo‘ydik.

💡 Qanday yo‘l tutish kerak?

Agar ikkita model faqat tashqi ko‘rinishi bilan o‘xshash bo‘lsa, ularni alohida qoldirgan yaxshi.

Agar ular haqiqatan ham bitta tushunchani ifodalasa, `shared`ga chiqarish mumkin.

🎯 Xulosa:

Bir xil kod har doim ham umumiy modulga chiqarilishi kerak degani emas.

Ba’zida 10 qator kodning takrorlanishi, ikkita feature’ni keraksiz bog‘lab qo‘yishdan yaxshiroq.

@flutterDevTalk
🧠 #algoritms
Boyer–Moore Majority Vote Algorithm

Array ichida n / 2 dan ko‘p uchraydigan elementni O(n) Time va O(1) Space bilan topish algoritmi.

💡 Asosiy g‘oya: Har xil ikkita element bir-birini bekor qiladi.


int majorityElement(List<int> nums) {
int candidate = 0, count = 0;

for (final num in nums) {
if (count == 0) candidate = num;
count += num == candidate ? 1 : -1;
}

return candidate;
}


Masalan: [2, 2, 1, 1, 1, 2, 2] → 2

⚠️ Majority element mavjudligi kafolatlanmagan bo‘lsa, natijani ikkinchi loop bilan tekshirish kerak.

@flutterDevTalk
Forwarded from Techie's Blog
Tekinga bo’lsa ham ishlab ko’rardim 😁
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3⚡1👍1😁1🤝1