The over-engineering boundary: when to simplify
Throughout this branch we showed the benefits of layers. This topic looks at the other side — their cost — because every abstraction charges a fee.
The official Flutter recommendations themselves strike this balance: some items are top priority (data/UI separation, repositories, MVVM, DI, immutable models, testing with fakes), while others are conditional (the domain layer, separate API/domain models, the specific state management choice).
The conditions on the conditional items are concrete:
- The domain layer (use cases) — only when complex logic crowds the view models. The guide considers it overhead in most apps.
- Separate API and domain models — recommended in large apps, extra verbosity in small ones.
The practical consequence: the "full set" architecture is not the right answer for every project. In a small project, the minimum set (service plus repository plus notifier) fully satisfies the highest-priority part of the official recommendations.
| Abstraction | Its cost | When it pays | When it does not |
|---|---|---|---|
| A repository contract (abstract) | One extra file | Always — the second implementation is the test fake | In a throwaway prototype with no tests |
| A separate DTO plus mapper | Two files plus a mapper test per model | Another team owns the backend; the JSON is dirty | You own the backend and the shape already matches |
| Use case classes | Four artifacts: file, provider, mock, test | Two repositories combine, or the logic repeats | Wrapping a single repository call |
| `Result` / `Either` | A `switch` at every call | Writes and multi-step operations | Simple reads — `AsyncNotifier` suffices |
| A `sealed` state union | N variants times M fields | Forms, wizards, mutually exclusive states | A simple list — `AsyncValue` suffices |
| Splitting into packages | `pubspec` management, codegen per package, `melos` | Several teams, shared code, a long lifetime | One developer, 15 screens |
| `freezed` for every model | `build_runner` time | JSON, unions, many fields | A two-field model — `Equatable` suffices |
Symptoms of over-engineering. These are concrete and observable:
- One-line classes.
class GetProductsUseCase { call() => _repo.fetchActive(); }— no logic, only forwarding. - An abstraction with a single user that is never even swapped in tests. An interface exists, one class
implementsit, and it will never change. - Navigation depth. Chasing one bug means opening six files: widget → notifier → use case → repository → mapper → service.
- Empty folders.
usecases/,entities/,datasources/— inherited from a template and never used. - Tests that are only mock setup. Twenty lines of
when(…)and oneexpect— such a test verifies no behaviour. - Your own "framework". Generic bases written for one feature:
BaseRepository<T>,BaseNotifier<S, E>.
The other side: symptoms of under-engineering. You need these too, to see the balance:
- Tests force you to mock HTTP or JSON.
- The same calculation repeats in three or more places.
- One backend field rename breaks five or more files.
- A
buildmethod is 200 lines long. - The user sees a
SocketExceptionmessage.
The check question. For each abstraction: "if I delete this, what gets worse?" The answer must be concrete ("I won't be able to replace the network in tests", "the second screen will copy the code"). If the answer is "it won't match clean architecture" — the abstraction has lost its reason.
╔═══ PROTOTİP (bir dəfəlik, 1-2 həftə ömür) ═══╗
✅ Widget + birbaşa sorğu — kifayətdir
❌ Repository, DTO, use-case, test — vaxt itkisi
Səbəb: kod silinəcək. Abstraksiyanın qazancı yaşamağa vaxt tapmır.
╔═══ KİÇİK (< 10 ekran, 1 developer, uzun ömür) ═══╗
✅ Service + repository (müqavilə ilə) + notifier
✅ Domain modelləri (immutable, biznes getter-ləri)
✅ Tipli Failure + dörd UI halı
✅ Repository və notifier testləri
⚠️ DTO: yalnız "çirkli" endpoint-lər üçün
❌ Use-case sinifləri (şərtlər ödənmir)
❌ Paketlərə bölmə
Qeyd: bu dəst rəsmi tövsiyələrin ƏN YÜKSƏK prioritetli
hissəsini tam ödəyir. "Yarımçıq" deyil.
╔═══ ORTA (10-40 ekran, 2-5 developer) ═══╗
✅ Yuxarıdakıların hamısı
✅ DTO + mapper (bütün endpoint-lər)
✅ Hibrid qovluq strukturu (ui/<feature>, data/ qat üzrə)
✅ Result (yazma əməliyyatları üçün)
✅ CI-də domain təmizliyi yoxlaması
⚠️ Use-case: yalnız şərt ödənən yerlərdə (3-5 ədəd normaldır)
❌ Paketlərə bölmə (hələ lazım deyil)
╔═══ BÖYÜK (40+ ekran, bir neçə komanda) ═══╗
✅ Yuxarıdakıların hamısı
✅ Use-case qatı (daha geniş)
✅ Paketlərə bölmə: domain, data, feature-lər
✅ melos + paket-səviyyəsində CI
✅ Golden testlər, integration test paketi
─── QAYDA ───
Dəsti YUXARIDAN AŞAĞI böyütmək lazımdır, əksinə deyil.
Kiçik layihəni "böyük" dəstlə başlamaq ən çox rast gəlinən
over-engineering formasıdır — və onu geri qaytarmaq çətindir.The recommended set by project size. Each line follows the priorities in the official recommendations.
In an interview this topic is a strong signal. "I apply clean architecture everywhere" is a weak answer; "which parts I apply and why" is a strong one. Referring to the official recommendations' priority split grounds that answer in a source: "I don't write a domain layer per feature, because the official guide treats it as conditional and our view models are still simple".
Practice. Write an audit for your own project (or a familiar one): list every abstraction and answer, in one sentence each, "if I delete this, what gets worse?"
Mark the abstractions whose answer is not concrete (only "it wouldn't match the principle") — those are deletion candidates.
Done means: the list holds at least eight abstractions, each with a concrete answer, and one or two candidates found (or a justified conclusion that all of them have a reason).
📚 Sources and documentation
- Architecture recommendationsofficialdocs.flutter.dev
The source for this topic: which items are top priority and which are conditional.
- Architecture guide: the optional domain layerofficialdocs.flutter.dev
The pros/cons list for use cases and the "add only when needed" advice.
- Architecture conceptsofficialdocs.flutter.dev
The principles themselves, free of templates — a reference point for simplification decisions.