Что такое Entity в архитектуре Flutter-приложения?

Ответ

Entity (Сущность) — это ядро бизнес-логики приложения, представляющее фундаментальные концепции предметной области. В моих Flutter-проектах, построенных по принципам Clean Architecture, Entity находятся в самом центре, не завися от фреймворков, UI или внешних сервисов.

Пример реализации Entity для приложения управления задачами:

// Value Object для идентификатора задачи
class TaskId {
  final String value;

  TaskId(this.value) {
    if (value.isEmpty) {
      throw ArgumentError('TaskId cannot be empty');
    }
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is TaskId &&
          runtimeType == other.runtimeType &&
          value == other.value;

  @override
  int get hashCode => value.hashCode;
}

// Value Object для статуса задачи
enum TaskStatus { todo, inProgress, done }

// Основная Entity - задача
class Task {
  final TaskId id;
  final String title;
  final String description;
  final TaskStatus status;
  final DateTime createdAt;
  final DateTime? completedAt;
  final List<String> tags;

  Task({
    required this.id,
    required this.title,
    required this.description,
    required this.status,
    required this.createdAt,
    this.completedAt,
    this.tags = const [],
  }) {
    // Бизнес-правила валидации
    if (title.isEmpty) {
      throw ArgumentError('Title cannot be empty');
    }

    if (completedAt != null && status != TaskStatus.done) {
      throw ArgumentError('Only completed tasks can have completion date');
    }
  }

  // Бизнес-методы (логика предметной области)
  Task markAsInProgress() {
    return Task(
      id: id,
      title: title,
      description: description,
      status: TaskStatus.inProgress,
      createdAt: createdAt,
      completedAt: completedAt,
      tags: tags,
    );
  }

  Task complete() {
    return Task(
      id: id,
      title: title,
      description: description,
      status: TaskStatus.done,
      createdAt: createdAt,
      completedAt: DateTime.now(),
      tags: tags,
    );
  }

  bool get isOverdue {
    final deadline = createdAt.add(const Duration(days: 7));
    return status != TaskStatus.done && DateTime.now().isAfter(deadline);
  }
}

Принципы работы с Entity в моих проектах:

  1. Независимость от фреймворка — Entity не импортируют flutter/dart:ui
  2. Богатая модель — содержат бизнес-правила и методы
  3. Иммутабельность — все поля final, методы возвращают новые экземпляры
  4. Value Objects — использование специализированных объектов для примитивов
  5. Легкая тестируемость — можно тестировать без Flutter-зависимостей

Преобразование между слоями:

// Из DTO в Entity
Task fromDto(TaskDto dto) {
  return Task(
    id: TaskId(dto.id),
    title: dto.title,
    description: dto.description,
    status: TaskStatus.values.firstWhere((e) => e.name == dto.status),
    createdAt: DateTime.parse(dto.createdAt),
    completedAt: dto.completedAt != null ? DateTime.parse(dto.completedAt!) : null,
    tags: dto.tags,
  );
}

Ответ 18+ 🔞

А, ну это про сущности, ёпта! Слушай, я тебе сейчас так объясню, что ты сам от себя охуеешь. Entity — это типа священный грааль твоего приложения, его фундамент, на котором всё держится. Представь, что это скелет бизнес-логики, который не должен ни от кого зависеть. Ни от Flutter, ни от UI, который каждые полгода меняется, ни от этих твоих внешних API, которые могут накрыться медным тазом в любой момент.

Вот смотри, как я в своих проектах это делаю. Беру, например, приложение для задач. Ну, там тудушки всякие. Так вот, первое правило — никакого Flutter внутри! Серьёзно, если ты импортируешь flutter/dart:ui в свою сущность, ты уже проебался. Это должно быть чистое ядро, которое можно взять и перенести хоть на сервер, хоть в консольное приложение — и оно будет работать.

Вот пример, как я это обожаю делать:

// Value Object для ID задачи. Не просто строка, а обёртка, чтоб не путать с другими ID.
class TaskId {
  final String value;

  TaskId(this.value) {
    if (value.isEmpty) {
      throw ArgumentError('TaskId cannot be empty'); // Защита от дурака
    }
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is TaskId &&
          runtimeType == other.runtimeType &&
          value == other.value;

  @override
  int get hashCode => value.hashCode;
}

// Статус задачи — тоже не просто строка, а enum. Чётко и понятно.
enum TaskStatus { todo, inProgress, done }

// А вот и главная сука — сама Task. Entity, мать её.
class Task {
  final TaskId id;
  final String title;
  final String description;
  final TaskStatus status;
  final DateTime createdAt;
  final DateTime? completedAt;
  final List<String> tags;

  Task({
    required this.id,
    required this.title,
    required this.description,
    required this.status,
    required this.createdAt,
    this.completedAt,
    this.tags = const [],
  }) {
    // А тут бизнес-правила, ёбать копать! Валидация на месте.
    if (title.isEmpty) {
      throw ArgumentError('Title cannot be empty'); // Без названия — не задача, а хуйня.
    }

    // Вот это важно! Нельзя иметь дату завершения у незавершённой задачи. Логика!
    if (completedAt != null && status != TaskStatus.done) {
      throw ArgumentError('Only completed tasks can have completion date');
    }
  }

  // Бизнес-методы! Это не просто геттеры-сеттеры. Это реальные действия предметной области.
  Task markAsInProgress() {
    // Иммутабельность, чувак! Не меняем старый объект, а создаём новый.
    return Task(
      id: id,
      title: title,
      description: description,
      status: TaskStatus.inProgress,
      createdAt: createdAt,
      completedAt: completedAt,
      tags: tags,
    );
  }

  Task complete() {
    return Task(
      id: id,
      title: title,
      description: description,
      status: TaskStatus.done,
      createdAt: createdAt,
      completedAt: DateTime.now(), // Запоминаем, когда завершили
      tags: tags,
    );
  }

  // Вычисляемое свойство — просрочена ли задача?
  bool get isOverdue {
    final deadline = createdAt.add(const Duration(days: 7)); // Допустим, неделя на выполнение
    return status != TaskStatus.done && DateTime.now().isAfter(deadline);
  }
}

Видишь, в чём фишка? Богатая модель. Сущность не просто мешок с данными, она знает, что с ней можно делать. Она сама себя валидирует. Её можно протестировать в полной изоляции, без всяких flutter test, просто обычным dart test. Доверия к такой штуке — овердохуища.

А когда нужно данные из внешнего мира (от API, например) превратить в эту самую Entity, делаем чёткий маппинг:

// Из DTO (Data Transfer Object, этакой грязной посылки с сервера) в нашу чистую Entity.
Task fromDto(TaskDto dto) {
  return Task(
    id: TaskId(dto.id),
    title: dto.title,
    description: dto.description,
    status: TaskStatus.values.firstWhere((e) => e.name == dto.status), // Преобразуем строку в enum
    createdAt: DateTime.parse(dto.createdAt),
    completedAt: dto.completedAt != null ? DateTime.parse(dto.completedAt!) : null,
    tags: dto.tags,
  );
}

Короче, суть в чём: Entity — это святое. Это твоя предметная область в чистом виде. Всё остальное — UI, базы данных, апишки — это просто шелуха вокруг этого ядра. Сделаешь так — и жить станет проще, а код перестанет быть пиздопроебибной кашей, где всё зависит ото всего.