Предметная модель инвентаря
Сервер из первого урока умеет отвечать на GET-запрос, но сообщение о готовности не решает задачу учёта. Теперь введём модель оборудования. Она станет общей опорой для таблицы, базы, форм и конфликтов редактирования. Если каждый следующий шаг придумывает новые поля, читатель изучает разные приложения; здесь имена и смысл объектов будут сохраняться по всей серии.
Рассмотрим две вымышленные позиции: клавиатуру KB-001 в офисе с остатком 12 и монитор MON-001 на складе с остатком 6. Это агрегированные карточки по артикулу, а не учёт каждого физического экземпляра по серийному номеру. Выбор влияет на операции: количество можно менять целым числом, тогда как перемещение конкретного устройства потребовало бы отдельного идентификатора экземпляра и иной модели хранения.
Какие поля определяют карточку
У карточки есть внутренний ID, внешний SKU, название Name, место Location, количество Quantity и версия Version. Внутренний идентификатор связывает карточку с адресами и журналом. Артикул помогает сотруднику найти знакомую позицию. Название объясняет, что хранится. Эти три поля похожи только на экране; заменять одно другим опасно. Переименование оборудования не должно превращать прежние движения в записи о другом объекте.
На этом шаге версия всегда равна единице. Она не обозначает модель клавиатуры и не является номером строки таблицы. Это технический счётчик состояния карточки, который понадобится для обнаружения конкурирующих изменений. Ввести поле сейчас полезно, чтобы позже не менять смысл структуры внезапно. Однако наличие счётчика само по себе пока ничего не защищает: условие обновления появится только в уроке о редактировании.
Структура и исходные данные находятся в main.go. Ниже приведён полный блок модели; остальной файл сохраняет сервер предыдущего шага и новый обработчик чтения из архива lesson-02.
type Item struct {
ID int64
SKU, Name, Location string
Quantity, Version int
}
var seed = []Item{
{1,"KB-001","Клавиатура","офис",12,1},
{2,"MON-001","Монитор","склад",6,1},
}
Порядок значений в коротком литерале соответствует порядку полей структуры. Такой способ удобен для двух учебных записей, но становится хрупким при расширении модели: добавление поля меняет позиционный договор. Для большой производственной модели предпочтительны именованные поля. Важно не запомнить одну форму записи как универсальную, а увидеть, что компилятор проверяет типы и количество значений, однако не знает смысл двух соседних строк одинакового типа.
Quantity имеет целочисленный тип, потому что учёт идёт в штуках. Дробный остаток клавиатуры не имеет смысла в выбранной предметной области. Для товара на вес потребовался бы другой договор единиц измерения; простая замена на число с плавающей точкой не определяет правила округления. В приложении обычно сначала устанавливают смысл величины, а затем выбирают её представление. Это уменьшает число технических решений, которые пришлось бы отменять позже.
Список как снимок состояния
Обработчик корня проходит по seed и формирует текстовые строки. После самостоятельного запуска ожидается вывод двух артикулов вместе с названиями, остатками и местами. Пока это не HTML-таблица: мы специально сохраняем простой формат, чтобы изменение модели не смешивалось с правилами шаблонов. Проекция данных в ответ отделена от самого набора записей. Один и тот же объект можно представить текстом, HTML или JSON, не меняя его предметного смысла.
Срез seed находится на уровне пакета и живёт вместе с процессом. Но наш код только читает его. Как только несколько обработчиков начинают изменять общую память, появляется вопрос одновременного доступа. HTTP-сервер может обслуживать разные запросы параллельно; порядок кликов в двух вкладках не задаёт порядок исполнения горутин. Сейчас мы не добавляем запись в срез именно потому, что постоянное хранилище и правила изменения будут рассмотрены отдельно.
Текстовый ответ показывает полезное различие между идентичностью и порядком. Клавиатура выводится первой, потому что так расположена в начальном срезе. Из этого не следует, что её ID всегда должен совпадать с номером строки. После фильтрации монитор может стать первой видимой строкой, сохранив свой ID. В следующей базе будем явно задавать порядок чтения: серверу нельзя поручить угадывать учебную последовательность по физическому расположению записей.
В нашей исходной модели одна карточка связывает артикул с одним местом. Поэтому клавиатура в офисе и такая же клавиатура на складе пока не описываются двумя независимыми остатками одного SKU. Если такая задача появится, понадобится разделение справочника товаров и остатков по локациям. Это содержательное расширение, а не дополнительное поле формы. Для текущего курса выбрана более узкая модель, чтобы сосредоточиться на серверных страницах, записи и согласованности операций.
Инварианты и граница проверки
Инвариант — условие, которое должно сохраняться после любой допустимой операции. Для «Инвентаря» количество не может быть отрицательным, артикул не должен повторяться, а карточка должна иметь название и место. В исходном литерале эти правила обеспечиваются автором данных. Структура Go сама не запрещает отрицательное число или пустую строку. Тип описывает представление, но предметная проверка обычно содержит дополнительные условия.
Рассмотрим ошибочный вариант: количество мониторов заменено на минус три. Программа с такой структурой может компилироваться и печатать строку, хотя результат противоречит договору учёта. Это показывает, почему проверку нельзя свести к фразе «код типизирован». Позже форма проверит вход пользователя, а база получит собственное ограничение количества. Две границы защищают разные пути попадания данных и не являются ненужным повторением одной проверки.
Другой вариант — два разных объекта с одинаковым SKU. Пока список позволяет это выразить, но человеку будет трудно понять, какую карточку он редактирует. В SQLite введём уникальность артикула. При этом внутренний ID останется самостоятельным: название или артикул не станут ключом адреса только ради удобства первой таблицы. Такая последовательность помогает избежать ситуации, когда исправление опечатки ломает ссылку на карточку.
Для самостоятельного изменения добавьте третью позицию с новым ID, новым артикулом и ненулевой версией. Проследите, какие поля действительно нужны текстовому ответу и какие пока только сохраняются в модели. Это небольшое упражнение показывает, что представление выбирает часть данных. В следующем уроке тот же выбор будет выполнять HTML-шаблон, а безопасность отображения перестанет зависеть от аккуратности ручного соединения строк.
Исходники урока
Оглавление серии · Скачать исходники. Полное самостоятельное состояние находится в lesson-02. Учебные приложения, зависимости, база и проверки при подготовке не запускались; описанный результат ожидается по исходникам. Материалы пока находятся в локальной редакционной версии.
Первичные источники: документация 1.