TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.75K subscribers
Post #3190 1.83K
День 2663. #ЗаметкиНаПолях #DDD
Что Такое Инварианты? Начало

Во многих «DDD-подобных» кодовых базах .NET бизнес правила разбросаны по обработчикам, валидаторам и контроллерам, и почти не затрагивают саму доменную модель. Каждая копия одного и того же правила со временем немного меняется, и валидность данного объекта начинает зависеть от того, каким путём вызывающий код к нему добрался. Вы, безусловно, можете построить работающую систему таким образом. Но есть более чистый способ, и он начинается с одной идеи.

Что такое инвариант?
Инвариант — это правило об объекте, которое должно оставаться верным до тех пор, пока объект существует.

Не только когда вы его сохраняете или когда запускается валидатор. Правило должно оставаться верным каждый раз, когда вы обращаетесь к объекту, независимо от того, как он попал в память.

Несколько примеров:
- У курса всегда есть непустое название.
- Итоговая сумма заказа всегда равна сумме стоимостей позиций в нём.
- Подписка находится ровно в одном состоянии: пробная, активная, истекшая или отменённая.

Ни в одном из этих случаев не упоминаются валидация, сохранение данных или HTTP. Это утверждения о предметной области, и они должны быть истинными независимо от способа загрузки объекта.

Где процедурный код дает сбои
Возьмём простой курс, написанный так, как это до сих пор делают большинство CRUD-подобных .NET-приложений:
public class Course
{
public string Title { get; set; }
public CourseStatus Status { get; set; }
public DateTime? PublishedOn { get; set; }
public decimal Price { get; set; }
}

В классе нет конструктора, и у каждого свойства есть публичный сеттер, поэтому он готов принимать любые комбинации значений.

Чтобы данные оставались корректными, правила разбросаны по всему приложению:
- CreateCourseValidator проверяет, не пуст ли заголовок.
- PublishCourseHandler устанавливает статус «Опубликован» и дату PublishedOn и не забывает проверить, не был ли курс уже в статусе «Опубликован».
- ChangePriceHandler проверяет, не отправлен ли курс в архив.
Появляется новая конечная точка, кто-то копирует существующий обработчик, и проверка архивности незаметно исчезает.
Каждое правило находится в месте, которое случайно оказывается на пути запроса. Ничто в самом классе Course не мешает ему перейти в недействительное состояние.

В этом и заключается реальная цена анемичной модели. Дело не в отсутствии поведения в классе. Дело в том, что класс не даёт никаких гарантий, поэтому каждый вызывающий объект должен самостоятельно обеспечивать соблюдение правил.

Решение простое: модель никогда не должна принимать недействительное состояние.

Если у вас есть ссылка на Course, вы можете ей доверять. Вам не нужна проверка if (course.Title is null), не нужно вызывать валидатор и не нужно надеяться, что обработчик до вас выполнил правильную проверку.

Окончание следует…

Источник:
https://www.milanjovanovic.tech/blog/what-invariants-are-and-why-a-domain-model-is-the-best-place-to-enforce-them
  • 👍 17
  • 👎 1
More from @netdeveloperdiary
  1. Sep 27, 2026День 2797. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Окончание Начало Продол…
  2. Sep 26, 2026День 2796. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Продолжение Начало Три…
  3. Sep 25, 2026День 2795. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Начало Проблема с позиц…
  4. Sep 24, 2026День 2794. #Оффтоп #Здоровье Сегодня будет необычный пост. Завтра в Москве стартует конфер…
  5. Sep 23, 2026День 2793. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
  6. Sep 22, 2026День 2792. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →