Моделирование предметной области — агрегаты, инварианты и службы
Моделируйте агрегаты, такие как Cart, с брендовыми идентификаторами и обеспечивайте инварианты с помощью фабрик и служб.
«Моделирование предметной области — агрегаты, инварианты и службы» — бесплатный урок TypeScript Academy на CoddyKit. Это урок 3 из 3. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения TypeScript Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс TypeScript Academy содержит 3 уроков всего.
Введение
Цель: создать агрегат Cart с помощью идентификаторов с брендами и обеспечить соблюдение таких правил, как количество > 0, в фабриках и службах.
- Идентификаторы с брендами различают границы сущностей
- Фабрики возвращают объединения Result
- Функции служб не допускают недопустимых переходов
Сущности и бренды
Идентификаторы с брендами делают CartId и ProductId несовместимыми, хотя оба являются строками. Это предотвращает ошибки смешения.
type Brand<Tag extends string, T> = T & { readonly __brand: Tag }
type CartId = Brand<"CartId", string>
type ProductId = Brand<"ProductId", string>
type Result<T> = { ok: true; value: T } | { ok: false; error: string }
const cartId = (s: string): CartId => s as CartId
const productId = (s: string): ProductId => s as ProductIdАгрегат и инвариант
Фабрики проверяют правила: количество должно быть > 0. Создавать можно только допустимые объекты Line.
type Money = number & { readonly __brand: "Money" }
const money = (n: number): Money => n as Money
interface Line { productId: ProductId; qty: number; price: Money }
interface Cart { id: CartId; lines: Line[]; total: Money }
function makeLine(productId: ProductId, qty: number, price: Money): Result<Line> {
if (!Number.isInteger(qty) || qty <= 0) return { ok: false, error: "qty must be > 0" }
return { ok: true, value: { productId, qty, price } }
}
function makeCart(id: CartId): Cart { return { id, lines: [], total: money(0) } }Служба: добавление товара
Функции служб выполняют безопасные переходы и пересчитывают итоги. Если итоги недопустимы, функция возвращает ошибку вместо повреждения состояния.
function addItem(cart: Cart, line: Line): Result<Cart> {
const lines = [...cart.lines, line]
const totalNum = lines.reduce((s, x) => s + (x.qty * (x.price as number)), 0)
if (!Number.isFinite(totalNum) || totalNum < 0) return { ok: false, error: "invalid total" }
return { ok: true, value: { ...cart, lines, total: (totalNum as unknown) as Money } }
}
// Usage
const c0 = makeCart(cartId("c1"))
const l = makeLine(productId("p1"), 2, money(10))
const c1 = l.ok ? addItem(c0, l.value) : { ok: false, error: "bad line" }Преобразование на границе
На границах системы (например, в APIs) преобразуйте данные в объекты передачи данных и проверяйте их при обратном разборе. Фабрики гарантируют корректность данных.
type CartDTO = { id: string; lines: { productId: string; qty: number; price: number }[]; total: number }
function serialize(c: Cart): CartDTO {
return {
id: c.id as unknown as string,
lines: c.lines.map(x => ({ productId: x.productId as unknown as string, qty: x.qty, price: x.price as unknown as number })),
total: c.total as unknown as number
}
}
function parse(dto: CartDTO): Result<Cart> {
const id = cartId(dto.id)
const lines: Line[] = []
for (const raw of dto.lines) {
const l = makeLine(productId(raw.productId), raw.qty, money(raw.price))
if (!l.ok) return { ok: false, error: l.error }
lines.push(l.value)
}
const totalNum = lines.reduce((s, x) => s + (x.qty * (x.price as number)), 0)
return { ok: true, value: { id, lines, total: money(totalNum) } }
}Советы
Лучшие практики:
- Централизуйте проверку в фабриках.
- Делайте функции служб чистыми и детерминированными.
- Преобразуйте бренды только на границах системы.
Проверка инварианта
Быстрая проверка: как следует обеспечивать соблюдение инварианта, например количество > 0?
Итоги
Итоги: идентификаторы с брендами в сочетании с фабриками и службами обеспечивают соблюдение инвариантов. Недопустимые состояния невозможно создать, поскольку все точки входа проверяют входные данные.
Часто задаваемые вопросы
Урок «Моделирование предметной области — агрегаты, инварианты и службы» бесплатный?
Да — полный текст урока «Моделирование предметной области — агрегаты, инварианты и службы» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс TypeScript Academy, подпишись на CoddyKit PRO. Курс TypeScript Academy содержит 3 уроков всего.
Чему я научусь в уроке «Моделирование предметной области — агрегаты, инварианты и службы»?
Моделируйте агрегаты, такие как Cart, с брендовыми идентификаторами и обеспечивайте инварианты с помощью фабрик и служб. Ты практикуешь TypeScript Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать TypeScript Academy?
Предыдущий опыт не требуется. TypeScript Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 3.
Сколько времени занимает урок «Моделирование предметной области — агрегаты, инварианты и службы»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке TypeScript Academy?
Да. Каждый урок TypeScript Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Брендированные и непрозрачные типы для предотвращения смешения единиц
- Идентификаторы с метками и шаблоны предметного моделирования
- Моделирование предметной области — агрегаты, инварианты и службы