Проектирование архитектуры и базы данных для учета аренды и ЖКХ
Проектирование архитектуры и базы данных для учета аренды и ЖКХ
Любое надежное мобильное приложение начинается с фундамента — грамотно спроектированной архитектуры и структуры хранения данных. Приложение для учета аренды и жилищно-коммунальных услуг (ЖКХ) работает с финансовыми показателями, историей платежей и тарифами. Ошибка в проектировании на начальном этапе приведет к тому, что при изменении стоимости киловатта электроэнергии у пользователей «поедет» вся история предыдущих расчетов.
Архитектура программного обеспечения — это совокупность важнейших решений об организации системы, выборе структурных элементов и их интерфейсов. В контексте мобильной разработки на фреймворке Flutter правильная архитектура позволяет разделить визуальную часть (кнопки, списки) и бизнес-логику (расчеты, сохранение в базу).
Выбор архитектурного паттерна
Для масштабируемых проектов в экосистеме Flutter стандартом де-факто является Clean Architecture (Чистая архитектура), часто в связке со стейт-менеджером BLoC (Business Logic Component). Этот подход делит приложение на независимые слои.
Архитектура определяет структуру, взаимосвязь между компонентами и их взаимодействия. Четко разработанная архитектура не только упрощает процесс разработки, но и обеспечивает масштабируемость, гибкость и удобство сопровождения кода.
В нашем приложении мы выделим три основных слоя:
- Слой данных (Data Layer): отвечает за работу с базой данных (БД) и внешними API. Здесь находятся SQL-запросы и модели данных.
- Доменный слой (Domain Layer): содержит чистую бизнес-логику. Например, именно здесь находится правило: «сумма к оплате за воду равна разнице показаний счетчика, умноженной на текущий тариф».
- Слой представления (Presentation Layer): экраны, виджеты и контроллеры состояний (BLoC), которые реагируют на действия пользователя.
Использование паттерна Репозиторий (Repository) позволяет скрыть от бизнес-логики то, откуда именно берутся данные. Доменный слой просто запрашивает getTariffs(), а репозиторий сам решает, достать их из локальной базы данных устройства или скачать с сервера.
Проектирование базы данных
Поскольку приложение должно вести строгий учет финансов и показаний, нам требуется реляционная база данных. В мобильной разработке для локального хранения чаще всего используется SQLite. Реляционная модель позволяет жестко связать арендаторов, их счетчики и справочник тарифов.
Сравним подходы к хранению данных, чтобы обосновать наш выбор:
| Характеристика | Реляционные БД (SQLite) | NoSQL БД (Hive, SharedPreferences) |
|---|---|---|
| Структура данных | Строгие таблицы со связями | Гибкие документы (JSON) |
| Сложные запросы | Поддерживаются (JOIN, агрегация) | Ограничены или отсутствуют |
| Целостность данных | Высокая (внешние ключи) | Зависит от кода приложения |
| Применение в нашем проекте | Идеально для связи тарифов, показаний и платежей | Подходит только для сохранения настроек темы (светлая/темная) |
Основные сущности и таблицы
Для реализации требований необходимо спроектировать несколько взаимосвязанных таблиц. Каждая таблица представляет собой отдельную сущность.
1. Таблица арендаторов (Tenants) Хранит базовую информацию о квартиросъемщиках.
id— уникальный идентификатор;name— ФИО или название;apartment_number— номер квартиры или комнаты;base_rent— фиксированная сумма арендной платы.
2. Справочник тарифов (Tariffs) Вынесение тарифов в отдельный справочник — критически важное архитектурное решение. Тарифы меняются (обычно раз в год), и если мы будем хранить стоимость киловатта прямо в таблице платежей, мы не сможем гибко управлять историей.
id— идентификатор тарифа;service_type— тип услуги (электричество, холодная вода, горячая вода);rate— стоимость за единицу (например, за 1 кВт·ч или 1 куб. м);effective_date— дата, с которой тариф вступает в силу.
3. Показания счетчиков (MeterReadings) Фиксирует данные приборов учета на определенную дату.
id— идентификатор записи;tenant_id— ссылка на арендатора (внешний ключ);service_type— тип услуги;value— показание счетчика;reading_date— дата снятия показаний.
4. Платежи (Payments) Объединяет все начисления и фактические оплаты.
id— идентификатор платежа;tenant_id— ссылка на арендатора;amount— сумма;payment_type— тип (аренда, электричество, вода);status— статус (оплачено, ожидается, просрочено);created_at— дата формирования счета.
Математика расчетов ЖКХ
Бизнес-логика приложения опирается на точные математические расчеты. Когда пользователь вводит новые показания счетчика, приложение должно найти предыдущее показание, вычислить разницу и умножить ее на актуальный тариф.
Формула расчета стоимости потребленного ресурса выглядит следующим образом:
Где:
- — итоговая стоимость к оплате (Cost);
- — текущие (новые) показания счетчика;
- — предыдущие показания счетчика;
- — ставка тарифа из справочника, актуальная на дату снятия показаний.
Рассмотрим на конкретном примере с числами. Допустим, в прошлом месяце показания счетчика электроэнергии составляли 1540 кВт·ч. В текущем месяце квартиросъемщик прислал фото счетчика с цифрой 1690 кВт·ч. В справочнике тарифов указано, что стоимость 1 кВт·ч составляет 5 рублей.
Сначала находим объем потребления: 1690 - 1540 = 150 кВт·ч. Затем умножаем объем на тариф: 150 × 5 = 750 рублей. Именно эта сумма должна быть записана в таблицу Payments со статусом «ожидается».
Связь базы данных и интерфейса
Чтобы данные из таблиц попали на экран смартфона, они должны пройти через все слои архитектуры. Процесс выглядит так:
Сначала пользователь открывает экран «Детали арендатора». Виджет отправляет событие LoadTenantDetails в контроллер состояния (BLoC). Контроллер обращается к доменному слою (Use Case), который запрашивает данные у Репозитория.
Репозиторий выполняет SQL-запрос к локальной базе данных SQLite. Например, чтобы получить историю платежей конкретного человека, выполняется запрос с использованием оператора WHERE tenant_id = 1. База данных возвращает сырые строки, которые Репозиторий преобразует в удобные объекты языка Dart (модели данных). Эти объекты передаются обратно в контроллер, который обновляет состояние экрана, и пользователь видит красивый список платежей.
Разделение тарифов и показаний счетчиков позволяет реализовать сложный, но необходимый функционал: перерасчет. Если выяснится, что тариф изменился задним числом, приложение сможет пройтись по таблице MeterReadings, взять разницу показаний и умножить на новое значение из таблицы Tariffs, автоматически обновив суммы в таблице Payments.
Проектирование базы данных — это процесс создания цифрового слепка реального мира. Чем точнее таблицы и связи отражают реальные процессы аренды и оплаты ЖКХ, тем проще будет добавлять новые функции в приложение в будущем.