Доменно-ориентированная валидация данных в JavaScript строится вокруг идеи, что структура и правила проверки должны отражать предметную область, а не техническую реализацию. В контексте Superstruct это означает, что валидационные схемы перестают быть набором разрозненных проверок и превращаются в выразительные описания бизнес-ограничений.
Библиотека Superstruct строит валидацию вокруг композиции простых структур. Каждая структура — это функция, которая принимает значение и либо возвращает его в нормализованном виде, либо выбрасывает ошибку.
Базовые примитивы:
import { string, number, boolean, array, object } from 'superstruct'
Каждый примитив уже содержит встроенную проверку:
string() // только строки
number() // только числа
boolean() // только boolean
Но ключевая особенность заключается в том, что эти примитивы не существуют изолированно. Они служат строительными блоками для предметных моделей.
Доменно-ориентированный подход требует описывать не типы данных, а сущности предметной области. Superstruct позволяет собирать такие сущности через композицию.
Пример модели пользователя:
import { object, string, number } from 'superstruct'
const User = object({
id: number(),
name: string(),
email: string()
})
На этом уровне структура уже отражает домен — пользователь как сущность системы, а не просто набор полей.
Однако реальная доменная модель редко ограничивается проверкой типов.
Доменные правила выражаются через модификаторы pattern,
size, min, max, а также через
кастомные проверки.
Пример ограничения email:
import { string, pattern } from 'superstruct'
const Email = pattern(
string(),
/^[^\s@]+@[^\s@]+\.[^\s@]+$/
)
Но бизнес-логика часто выходит за рамки регулярных выражений. Для
этого используется refine.
refine позволяет добавлять произвольные условия,
превращая структуру в отражение доменной логики.
import { refine, string } from 'superstruct'
const StrongPassword = refine(string(), 'StrongPassword', (value) => {
return value.length >= 8 && /[A-Z]/.test(value) && /\d/.test(value)
})
Здесь уже проявляется доменная специфика: пароль — это не просто строка, а строка, удовлетворяющая политике безопасности системы.
В реальных приложениях сущности редко состоят из примитивов. Они комбинируются из других доменных структур.
import { object, string, number, refine } from 'superstruct'
const PositiveNumber = refine(number(), 'PositiveNumber', (v) => v > 0)
const Product = object({
id: string(),
price: PositiveNumber,
title: string()
})
Такое разбиение позволяет переиспользовать доменные ограничения без дублирования логики.
Важной частью доменной валидации является приведение данных к ожидаемому виду. Superstruct поддерживает трансформации через кастомные структуры.
Пример нормализации строк:
import { coerce, string } from 'superstruct'
const TrimmedString = coerce(string(), string(), (value) => value.trim())
Это позволяет встроить обработку данных в саму доменную модель, а не выносить её в отдельный слой.
В предметно-ориентированном подходе объект часто рассматривается как агрегат — целостная единица с внутренними правилами.
import { object, string, array, number, refine } from 'superstruct'
const OrderItem = object({
productId: string(),
quantity: refine(number(), 'Quantity', (v) => v > 0)
})
const Order = object({
id: string(),
items: array(OrderItem)
})
Здесь важно, что правила применяются не только к полям, но и к структуре в целом.
Иногда ограничения касаются всей структуры целиком. Например, заказ не может быть пустым.
import { refine, array } from 'superstruct'
const NonEmptyArray = (struct) =>
refine(array(struct), 'NonEmptyArray', (value) => value.length > 0)
Такой подход отражает бизнес-правило на уровне агрегата, а не отдельного поля.
Superstruct возвращает структурированные ошибки, которые можно интерпретировать как часть доменной логики.
import { validate } from 'superstruct'
const [error, result] = validate(data, User)
Ошибки содержат путь до некорректного поля, что позволяет точно связывать проблему с доменной сущностью.
При использовании TypeScript Superstruct позволяет извлекать типы напрямую из структур.
import { Infer, object, string } from 'superstruct'
const User = object({
id: string(),
name: string()
})
type UserType = Infer<typeof User>
Это обеспечивает синхронизацию между валидацией и типами данных, исключая рассогласование модели и реализации.
Одним из ключевых принципов является повторное использование ограничений.
const UUID = pattern(string(), /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i)
const UserId = UUID
const OrderId = UUID
Одна и та же структура используется в разных контекстах домена, сохраняя согласованность правил.
Сложные домены требуют разбиения на независимые структуры.
const Address = object({
city: string(),
street: string(),
zip: string()
})
const UserProfile = object({
name: string(),
address: Address
})
Такой подход позволяет отражать иерархию предметной области напрямую в коде.
Инварианты — это условия, которые всегда должны быть истинными. В
Superstruct они выражаются через комбинацию refine и
композиции структур.
const Age = refine(number(), 'Age', (v) => v >= 0 && v <= 120)
Инварианты становятся частью модели, а не внешней логики.
В некоторых доменах структура зависит от условий.
const createUserStruct = (isAdmin) =>
object({
name: string(),
role: isAdmin ? string() : pattern(string(), /^user$/)
})
Это позволяет моделировать контекстно-зависимые правила.
Валидация служит не только проверкой данных, но и границей между слоями системы. Superstruct формирует чёткий контракт входных данных, отделяя внешние источники от внутренней модели.
Любой внешний объект должен пройти через структуру, соответствующую доменной модели, прежде чем попасть в бизнес-логику.
Основная сила подхода заключается в том, что сложные правила строятся из простых элементов:
Так формируется система, в которой валидация становится отражением предметной области, а не техническим слоем проверки данных.