Clean Architecture și Modelarea Domeniului în Dart: Sealed Classes, Imutabilitate și Rezultate Funcționale în Dart

Ghid arhitectural de producție pentru aplicații enterprise în Dart: implementarea straturilor Domain, Data și Presentation conform Clean Architecture, modelarea stărilor cu sealed classes, imutabilitate și tratarea erorilor ca valori prin tipul Result / Either.

Sintaxă
sealed class DomainResult<T> {} class Success<T>(this.data); class Failure(this.error);

Pe măsură ce bazele de cod cresc, lipsa unei arhitecturi bine delimitate transformă aplicațiile în „monoliți spaghetti”: logica de interfață grafică se amestecă cu apelurile HTTP, modificările de schemă în API rup componentele vizuale, iar scrierea testelor unitare devine practic imposibilă.

Clean Architecture (popularizată de Robert C. Martin) rezolvă aceste provocări prin aplicarea Regulii Dependențelor: codul de afaceri pur (Domain Layer) nu trebuie să depindă de niciun framework extern (Flutter, Shelf), de nicio bază de date și de niciun client HTTP. În Dart 3, această arhitectură atinge cel mai înalt nivel de ergonomie datorită combinației dintre Sealed Classes, Records și Pattern Matching.

Separarea pe Straturi în Clean Architecture Dart
# Stratul Exterior (Frameworks & Drivers):
#    [Flutter UI / Widgets]          [Bază de Date SQLite / SharedPreferences / HTTP Client]
#              │                                              │
#              ▼                                              ▼
# ═════════════╪══════════════════════════════════════════════╪════════════════════════════
# Stratul de Prezentare & Date:                               │
#    [Controllers / BLoC / ViewModels]                        │
#              │                                              ▼
#              │                                     [Repository Implementations]
#              ▼                                              │
# ═════════════╪══════════════════════════════════════════════╪════════════════════════════
# Nucleul Central Pur (Domain Layer - FĂRĂ dependințe externe!):
#              ▼                                              ▼
#    [UseCases / Interactors] ───► [Repository Interfaces] ◄──┘
#              │
#              ▼
#    [Entities & Value Objects (Sealed Classes & Records)]

Tabel Comparativ: Excepții Runtime vs Erori Funcționale ca Valori (Result)

Criteriu Arhitectural Excepții Tradiționale (throw Exception) Rezultate Funcționale (sealed class Result<T>)
Semnătura Funcției Ascunde eșecul: declară că returnează User, dar poate crăpa la runtime. Onestă: declară explicit că returnează Result<User, AuthError>.
Verificare la Compilare Neverificată (Compilatorul nu forțează existența unui catch). Exhaustivă (Dart forțează tratarea atât a succesului cât și a erorii).
Cost de Performanță Alocare grea a stivei de apel (stack trace capturing). Alocare minimă pe heap, fără colectare de stivă în caz de eroare de business.
Domain Layer: Value Objects și Result Tipizat
// 1. Tipul Result implementat ca Sealed Class
sealed class Result {
  const Result();
}

final class Success extends Result {
  final S value;
  const Success(this.value);
}

final class Failure extends Result {
  final E error;
  const Failure(this.error);
}

// 2. Ierarhie strictă de erori de afaceri
sealed class AuthFailure {
  const AuthFailure();
}

final class InvalidEmailFormat extends AuthFailure {}
final class WeakPassword extends AuthFailure { final int minLength; WeakPassword(this.minLength); }
final class UserAlreadyExists extends AuthFailure {}
final class NetworkUnavailable extends AuthFailure {}

// 3. Value Object cu validare inclusă
extension type const EmailAddress._(String value) {
  static Result create(String input) {
    final regex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
    if (!regex.hasMatch(input)) {
      return const Failure(InvalidEmailFormat());
    }
    return Success(EmailAddress._(input));
  }
}
UseCase și Evaluare Exhaustivă în Presentation Layer
// Contractul Repository-ului în Domain Layer (pur abstract)
abstract interface class AuthRepository {
  Future> registerUser({
    required EmailAddress email,
    required String password,
  });
}

// UseCase pur de afaceri
class RegisterUserUseCase {
  final AuthRepository repository;
  RegisterUserUseCase(this.repository);

  Future> call(String rawEmail, String password) async {
    // Validare Email:
    final emailResult = EmailAddress.create(rawEmail);
    if (emailResult is Failure) {
      return Failure(emailResult.error);
    }
    final validEmail = (emailResult as Success).value;

    // Validare Parolă:
    if (password.length < 8) {
      return Failure(WeakPassword(8));
    }

    return repository.registerUser(email: validEmail, password: password);
  }
}

// Consum în Stratul de Prezentare (UI Controller) cu Pattern Matching:
void handleRegistrationUI(Result outcome) {
  final message = switch (outcome) {
    Success(:final value) => 'Înregistrare reușită! ID Utilizator: $value',
    Failure(error: InvalidEmailFormat()) => 'Adresa de email introdusă este invalidă.',
    Failure(error: WeakPassword(:final minLength)) => 'Parola trebuie să aibă minim $minLength caractere!',
    Failure(error: UserAlreadyExists()) => 'Acest cont există deja în baza de date.',
    Failure(error: NetworkUnavailable()) => 'Conexiunea la internet este indisponibilă.'
  };

  print('[UI UPDATE]: $message');
}