Рассказываю о своем диплом проекте: Веб-ориентированная АИС «Инвентарис».
Что есть инвентаризация? Что есть бриф? Об этом и узнаем.
2472 слов
16–20 мин. чтения
Немного о важном
«Прежде чем написать первую строку кода, убедитесь, что законодатель уже не написал её за вас.»
Инвентаризация вам не это
Есть одна категория проектов, которую разработчики почему-то очень любят недооценивать.
Это не банковские системы. Не медицинские информационные комплексы. Даже не государственные порталы, где каждый второй запрос сопровождается молитвой и резервной копией. Это обычная… инвентаризация.
Слово настолько скучное, что хочется немедленно закрыть вкладку и пойти писать очередной менеджер задач. Кажется, будто вся предметная область сводится к одной таблице:
ID | Название | Кабинет | Ответственный
Пара кнопок «Добавить», «Удалить», «Изменить», зелёная галочка и можно идти пить чай.
К сожалению, законодательство с подобным оптимизмом категорически не согласно.
Очень быстро выясняется, что инвентаризация существует не потому, что программисту захотелось написать красивый CRUD. Она существует потому, что существует бухгалтерский учёт, материальная ответственность, жизненный цикл имущества и целый набор нормативных документов, определяющих, что именно считается правильным учётом. В предметной области системы для учёта оборудования именно это становится отправной точкой проектирования.
Именно здесь проходит довольно неприятная граница между «написал программу» и «разработал информационную систему».
Я никогда не был против использования искусственного интеллекта при разработке. Если инструмент позволяет быстрее написать код, замечательно. Проблема начинается немного раньше… …В тот момент, когда разработчик открывает IDE, не открыв Консультант Плюс (или любой другой аналог справочно-правовой системы в Вашей юрисдикции).
Можно бесконечно генерировать контроллеры, ORM-модели и REST API. Всё это будет работать. Пока не выяснится, что половина процессов противоречит приказам министерства финансов, другая половина не учитывает реальные сценарии работы организации, а оставшаяся часть просто не отвечает на главный вопрос:
зачем вообще существует эта система?
В случае АИС «Инвентарис» задача с самого начала формулировалась достаточно чётко: централизованный учёт оборудования, контроль его состояния, фиксация перемещений, привязка объектов к помещениям и ответственным лицам, а также формирование необходимой отчётности.
Заметьте одну интересную деталь!
Нигде не написано:
«Создать красивый сайт.»
Не написано:
«Использовать Flask.»
Не написано:
«Написать PostgreSQL-запросы.»
Потому что программирование является лишь способом реализации требований, а вовсе не их источником. Программирование не более чем навык, который может освоить практически каждый. И, если честно, в современном мире это далеко не самое ценное умение. Лучше будьте инженерами. Будьте великими разработчиками. Не становитесь людьми, которые умеют лишь «буковки печатать».
Я немного отвлёкся. Давайте вернёмся к теме грустной реальности (как будто до этого было весело)? Подводя итоги этого заголовка третьего уровня могу смело утверждать, что любой разработчик, который хотя бы однажды сталкивался с автоматизацией реальных процессов, довольно быстро обнаруживает неприятную истину.
Мы все подневольны.
Подневольны законодательству.
Подневольны внутренним регламентам организации.
Подневольны техническому заданию.
Подневольны ожиданиям заказчика.
И если хоть один из этих элементов проигнорировать, проект очень быстро окажется перед классическим выбором между двумя стульями. Причём оба обычно подписаны словами «переделать всё заново».
«Но ведь ты и раньше писал большие проекты. Почему здесь всё настолько серьёзно?»
Ответ довольно простой:
Потому что это уже не совсем pet-проект.
Не поймите неправильно. На самом деле «КОММУ» проходила тоже примерно такой же путь проектирования и бла бла бла, но… Во-первых, в той статье гораздо интереснее было рассказать про рефлексию дизайнера, а тут уже про «правила хорошего разработчика». Во-вторых, мнение кардинально не менялось: личные проекты являются лучшим способом чему-либо научиться. Более того, именно благодаря им вообще появилась большая часть моего опыта. Но между системой, которую ты пишешь для себя, и системой, которая моделирует реальные процессы организации, существует огромная разница.
В первом случае вполне можно позволить себе открыть PlantUML, набросать диаграмму вариантов использования, подумать пять минут и перейти непосредственно к реализации. Бога ради! Декомпозируй в голове. Никто не запрещает. Иногда это даже правильно.
Интересно что такое декомпозиция? Нажми на меня!
Декомпозиция — это процесс, при котором мы берём сложную задачу или систему и делим её на множество более простых частей. Как заварить чай? На первый взгляд это одна простая задача, но её тоже можно декомпозировать. Давайте разобьём процесс на 5 шагов:
Налить воду в чайник и включить его.
Положить чайный пакетик в пустую чашку.
После того как вода закипит, залить пакетик кипятком.
Подождать 3-5 минут, пока чай заварится.
Вытащить пакетик и выбросить его в мусор.
Оставляю вам домашнее задание: проведите декомпозицию процесса ядерного синтеза плутона. Блок A должен быть декомпозирован минимум до шестого уровня детализации. Дополнительно декомпозируйте несколько процессов уровня A, например A1.2 и A1.5. Удачи!
Во втором случае подобный подход начинает разваливаться практически сразу.
Допустим, заказчик произносит всего одну фразу:
«Нам нужна система учёта оборудования.»
Рисунок 1. А ты хотел по простому?
ЧТО ТУТ СЛОЖНОГО? Пять минут работы! Сейчас быстренько нагенерируем что-нибудь в… Эм… GigaChat. Да ведь? А знаете, что Вы на самом деле должны были сделать? Как минимум задать следующие вопросы:
Что именно считается оборудованием?
Кто имеет право его регистрировать?
Можно ли удалять записи? (НЕТ! НИКОГДА!)
Что делать при списании?
Кто подтверждает перемещение между кабинетами?
Как долго хранится история?
Какие роли существуют в системе?
Какие данные являются критическими?
На каком сервере всё будет работать?
Требуется ли интеграция с LDAP?
Нужно ли журналирование действий?
Нужно ли резервное копирование?
Большая часть этих вопросов вообще никак не относится к программированию. Но именно они определяют, какой получится программа.
Да, ещё существуют Федеральный закон «О бухгалтерском учёте» № 402-ФЗ, приказ Минфина России № 204н, приказ Минфина России № 4н… И ещё целая пачка нормативно-правовых актов, КОТОРЫЕ ТЫ ДОЛЖЕН БЫЛ ПРОЧИТАТЬ ПЕРЕД ТЕМ, КАК ВООБЩЕ ОТКРЫТЬ ОКНО ВВОДА ЗАПРОСА К ИИ!
Закрыл.
Вкладку.
С ИИ.
Открывай справочно-правовую систему, действующую в твоей юрисдикции. Сначала разберись, что ты должен разработать. И только потом спрашивай у нейросети, как это реализовать.
По этой причине разработка «Инвентариса» начиналась вовсе не с написания базы данных.
Сначала появился бриф → Потом общая концепция → Затем техническое задание.
И только после этого стало возможным переходить к проектированию самой системы. Именно такая последовательность документов зафиксирована в пояснительной записке к диплому и именно эти документы являются базой перед началом проектирования.
Мне кажется, именно здесь многие начинающие разработчики совершают одну и ту же ошибку. Они воспринимают документацию как нечто, что существует исключительно ради преподавателя, менеджера проекта или какого-нибудь особенно сурового аудитора. На практике всё оказывается гораздо скучнее ведь документация существует ради самого разработчика.
Почему? Потому что память человека удивительно плохо подходит для хранения проектов продолжительностью несколько месяцев. Давайте! Попробуйте через полгода вспомнить, почему в системе существует определённое ограничение. Почему именно такая модель данных. Почему пользователь не может удалить оборудование. Почему журналирование обязательно.
Да потому что если нигде этого не записано, единственным носителем знаний остаётся автор проекта. А авторы, как показывает практика, обладают крайне неприятной привычкой забывать собственные архитектурные решения уже через несколько недель. Поэтому я совершенно перестал воспринимать бриф и техническое задание как очередную бумагу, необходимую для защиты диплома. Они стали своеобразной страховкой от самого себя. И, как выяснилось позже, довольно неплохой.
P.s. Нажми, чтобы открыть.
Не хочу хвастаться (хотя… если честно, именно для этого и существуют личные блоги), но во время защиты дипломного проекта мне действительно пригодились документы, которые были разработаны как часть самой системы. Да, да. Я буквально распечатал выдержки из них вместе с выдержками из федеральных законов. В документации уже было расписано, каким требованиям законодательства соответствует каждая часть АИС, а также подробно описан выбранный стек разработки.
Давайте создавать
Во второй части мы наконец перейдём к самому проектированию и поговорим о вещи, которая почему-то вызывает у студентов почти физическую боль.
Нет, не о ГОСТах. О диаграммах.
Точнее, о том моменте, когда внезапно выясняется, что прямоугольники и стрелочки, безусловно, были созданы исключительно для концентрации страданий всех субъектов на планете Земля, однако разработчики почему-то продолжают пользоваться ими уже несколько десятилетий. Видимо, не только из чувства мазохизма.
Проектируем
Как бы странно это ни звучало, но написать код в подобных проектах зачастую оказывается проще, чем его спроектировать.
После появления технического задания появляется закономерное желание немедленно открыть IDE, создать первый маршрут Flask и начать героически писать SQL-запросы. Проблема лишь в том, что программа пока существует исключительно в голове разработчика, а минусы человеческой памяти были описаны приблизительно за 5 абзацев до этого. Помните? Нет? Ну так поэтому прежде чем появилась первая таблица MariaDB и первая HTML-страница, пришлось сделать то, что не любит практически каждый студент, увидевший слова IDEF0 впервые.
Рисовать диаграммы.
Нет, не потому что так захотел разработчик (он этого не хочет). И даже не потому что так требует очередной ГОСТ.
А потому что одна диаграмма способна ответить на вопросы, на которые потом пришлось бы отвечать несколькими сотнями строк кода.
Функциональная модель IDEF0Диаграмма «Сущность Связь»Use Case диаграмма
Галерея 1: IDEF0 • Диаграмма вариантов использования • IDEF1X
Разумеется, подробно рассматривать каждую из них в рамках статьи было бы несколько жестоко по отношению к читателю. Если Вам действительно интересно разобраться в каждой стрелочке, подписи и обозначении, настоятельно рекомендую обратиться к пояснительной записке (часть персональных данных скрыта для соблюдения № 152-ФЗ). Там все проектные материалы приведены полностью вместе с описанием и спецификациями. Здесь же ограничимся кратким обзором того, какую задачу решала каждая из них.
Первой появилась IDEF0. Её задача максимально проста: ответить на вопрос «что вообще происходит внутри процесса регистрации оборудования?». На контекстной диаграмме отображается весь процесс целиком, после чего он последовательно декомпозируется на более мелкие операции. В моём случае регистрация оборудования перестала быть банальной записью новой строки в таблицу и превратилась в последовательность действий: проверка полномочий пользователя, получение исходных данных, проверка их корректности, обращение к базе данных и фиксация результата в журнале событий. Помимо самих действий модель также показывает входные данные, управляющие воздействия, механизмы выполнения и конечный результат процесса. Именно благодаря этому становится очевидно, что регистрация оборудования представляет собой полноценный бизнес-процесс, а не одну SQL-команду.
Функциональная модель IDEF0Декомпозиция А0 до 4 уровняДекомпозиция А1 до 3 уровня
Галерея 2: Функциональная модель и её декомпозиции
После этого (ещё была вариантов использования, но она скучная даже для статьи) настало время IDEF1X. Пожалуй, именно эта диаграмма оказалась для меня наиболее интересной, поскольку она напрямую связывает требования предметной области с будущей структурой базы данных. Здесь уже появляются сущности, их атрибуты, первичные и внешние ключи, а также связи между таблицами. Центральным объектом модели становится оборудование, вокруг которого постепенно выстраиваются категории, помещения, поставщики, статусы, перемещения, обслуживание, пользователи и множество других сущностей.
Рисунок 2. Диаграмма «Сущность Связь»
Последней была подготовлена диаграмма последовательностей. Если предыдущие модели описывали структуру системы, то здесь внимание уже сосредоточено на её поведении. Диаграмма показывает, каким образом взаимодействуют пользователь, веб-интерфейс, сервер приложения, база данных и сессионное хранилище во время выполнения основных сценариев работы. В качестве примеров были выбраны авторизация пользователя, загрузка списка оборудования и формирование отчётов. При этом отдельно рассматриваются не только успешные сценарии, но и типовые ошибки: недействительная сессия, недостаточные права доступа, ошибка авторизации и другие ситуации, которые в реальной эксплуатации возникают значительно чаще, чем хотелось бы любому разработчику.
Процесс авторизацииПроцесс загрузкиПроцесс отчёта
Галерея 3. Диаграммы последовательностей: авторизация • загрузка оборудования • формирование отчёта
Именно поэтому я старался не воспринимать проектирование как очередную бюрократическую процедуру. Оно не заменяет разработку и уж точно не делает её проще (если бы). Оно делает гораздо более важную вещь: позволяет убедиться, что будущая система хотя бы не начнёт противоречить самой себе ещё до того, как появится первая строка кода. По сути, диаграммы стали промежуточным этапом между техническим заданием и реализацией физической модели, связав требования предметной области с архитектурой будущего программного продукта.
И только после этого можно было наконец открыть IDE и заняться тем, чего все обычно ожидают от разработчика. Написанием кода.
Пишем код
После завершения проектирования наступает тот самый этап, которого обычно все и ждут, услышав слово «разработка». Flask, SQL-запросы, маршруты, шаблоны, авторизация… Словом, начинается написание кода. Однако даже здесь предметная область продолжает диктовать свои правила, а многие архитектурные решения были приняты задолго до появления первого @app.route.
Одним из наиболее интересных решений стала версионность карточек оборудования. В большинстве простых CRUD-приложений изменение записи означает изменение строки в базе данных. В «Инвентарис» подобный подход оказался неприемлем. Если пользователь меняет помещение, статус или другую значимую характеристику оборудования, прежнее состояние не должно исчезнуть. Вместо этого система создаёт новую актуальную версию карточки, а предыдущая остаётся в базе данных в качестве архивной. Благодаря такому подходу сохраняется полная история изменений, а при возникновении спорной ситуации всегда можно восстановить последовательность действий пользователя. Более того, архивные карточки запрещено редактировать, что исключает возможность случайного изменения исторических данных. Ниже представлена функция, которая отвечает за создание новой версии оборудования. Не шедевр, но хоть что-то.
<... пропущен рендер при GET запросе ...>
if request.method == "POST":
try:
# !Как видишь сначала мы проверяем на фальсификацию
if not is_actual_equipment(item_id):
flash("Редактирование архивной версии запрещено. Откройте актуальную запись.", "warning")
return redirect(url_for("equipment.list_equipment"))
# !Конечно же нельзя "перезаписать" на другое оборудование
# !Либо пишем на него же, либо другой инвентарник
data, errors = read_equipment_form()
if data["inventory_number"] and db.fetch_all(
"equipment_inventory_exists_for_other",
data["inventory_number"],
item_id,
):
errors.append("Актуальное оборудование с таким инвентарным номером уже существует.")
if errors:
for error in errors:
flash(error, "warning")
data["id"] = item_id
return render_template("equipment_form.html", item=data, references=references, mode="edit")
new_id = create_equipment_version(db, item_id, data)
if not new_id:
flash("Не удалось создать новую версию оборудования.", "danger")
return redirect(url_for("equipment.view_equipment", item_id=item_id))
flash("Создана новая версия карточки оборудования.", "success")
return redirect(url_for("equipment.view_equipment", item_id=new_id))
При этом сама версионность не заменяет журнал перемещений, а лишь дополняет его. Например, при переносе оборудования в другое помещение система сначала создаёт новую версию карточки с обновлёнными сведениями, после чего фиксирует сам факт операции в отдельном журнале. В результате текущий список оборудования всегда отображает актуальное состояние, тогда как журнал позволяет определить, кто, когда и по какой причине выполнил перемещение.
Не менее важной частью проекта стала модель разграничения доступа ESR, разработанная специально для данной системы. Вместо десятков отдельных булевых флагов было решено использовать компактную трёхзначную строку, где каждая позиция соответствует одному из функциональных контуров приложения: Equipment, System и Reports. Каждая цифра принимает значение от 1 до 3, обозначая отсутствие доступа, чтение или запись соответственно. Благодаря этому права пользователя описываются всего тремя символами, а проверка доступа сводится к простому посимвольному сравнению строки пользователя со строкой, требуемой конкретным маршрутом. Например, роль 311 позволяет создавать и изменять оборудование, но не предоставляет доступа к системному администрированию и отчётам, тогда как 333 соответствует полному административному доступу. Давай посмотрим код?
# !Допустим нам нужно убедиться, что человек имеет доступ к чтению оборудованию
@equipment_bp.route("/<int:item_id>")
@require_permissions("211")
def view_equipment(item_id: int) -> Any:
pass
# !Вызываем декоратор require_permissions и говорим ему, что как минимум нужно
# !иметь право чтения (2) в контуре оборудования (E)
<... Пропускаем скучный код и идём прямо к декоратору ...>
# !На всякий случай проверяем, что в БД не лезли
# !Нам интересна только схожесть с системой прав
def permissions_value(value: Any) -> str:
raw = str(value or "").strip()
if len(raw) != 3 or any(char not in "123" for char in raw):
return "111"
return raw
# !По очереди идём по каждому числу из необходимых и имеющихся
# !Какое-то число меньше? Тогда будет 403
def has_permissions(user_permissions: Any, required_permissions: str) -> bool:
current = permissions_value(user_permissions)
required = permissions_value(required_permissions)
for current_digit, required_digit in zip(current, required):
if int(current_digit) < int(required_digit):
return False
return True
# !Загружаем права из сессии (не переживай, они с БД сверяются)
def current_permissions() -> str:
return permissions_value(session.get("permissions"))
# !Обёртка, которая всё соберёт
def can(required_permissions: str) -> bool:
return has_permissions(current_permissions(), required_permissions)
# !Сам код декоратора. Он вызывает обёртку и отказывает если прав нет
def require_permissions(required_permissions: str) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
def decorator(view_func: Callable[..., Any]) -> Callable[..., Any]:
@wraps(view_func)
@login_required
def wrapper(*args: Any, **kwargs: Any) -> Any:
if not can(required_permissions):
abort(403)
return view_func(*args, **kwargs)
return wrapper
return decorator
Подобная схема позволила централизовать механизм безопасности. После успешной авторизации Flask сохраняет сведения о пользователе в сессии, а каждый защищённый маршрут дополнительно проверяется при помощи собственного декоратора. Если пользователь не авторизован, его перенаправляют на страницу входа. Если же прав недостаточно, выполнение обработчика прекращается ещё до начала основной логики приложения, а сервер возвращает страницу 403 Forbidden. Такой подход исключает частичное выполнение операций и предотвращает обход ограничений даже при прямом вводе адреса страницы в браузере.
Помимо проверки прав в приложении реализован и ряд прикладных механизмов защиты целостности данных. Система контролирует обязательность заполнения полей, существование ссылочных записей, корректность форматов дат и числовых значений, уникальность логинов пользователей, а также уникальность актуального инвентарного номера оборудования. Подобные проверки не относятся напрямую к модели ESR, однако позволяют предотвратить появление противоречивых или заведомо некорректных данных ещё до их сохранения в базе.
В конечном итоге большая часть программного кода оказалась посвящена вовсе не отображению HTML-страниц и работе с SQL-запросами, а обеспечению корректности предметной области. Ведь написать форму создания оборудования значительно проще, чем гарантировать, что спустя несколько лет эксплуатации никто случайно не уничтожит историю изменений, не выдаст бухгалтеру права администратора или не создаст две актуальные карточки с одним и тем же инвентарным номером. Именно подобные ограничения и отличают информационную систему от очередного набора CRUD-страниц.
Вместо вывода
На этом, пожалуй, стоит поставить точку. Это всё таки публицистическая статья, а не полноценный анализ, не так ли? «Инвентарис» оказался не просто дипломным проектом, а возможностью снова пройти весь путь разработки информационной системы: от изучения законодательства и проектирования до реализации, тестирования и подготовки документации.
Если Вам стало интересно узнать о проекте больше, ниже собраны материалы, которые могут оказаться полезными:
GitHub-репозиторий — если интересно заглянуть в исходный код, посмотреть архитектурные решения или просто убедиться, что всё действительно работает.
Руководство пользователя и администратора системы — если интересно посмотреть, как устроено взаимодействие с системой, какие сценарии работы поддерживаются и каким образом реализовано администрирование.
Пояснительная записка к дипломному проекту — почти 140 страниц проектирования, анализа, диаграмм, технических решений и документации. Да, часть страниц украшена чёрными плашками. Как иначе соблюдать сохранность чужих персональных данных? Зато всё остальное осталось на месте, и, если вдруг захочется погрузиться в детали, материала там более чем достаточно.