Проект “Погода” #
Веб-приложение для просмотра текущей погоды. Пользователь может зарегистрироваться и добавить в коллекцию одну или несколько локаций (городов, сёл, других пунктов), после чего главная страница приложения начинает отображать список локаций с их текущей погодой.
Что нужно знать #
- Go - std lib, http, пакеты, интерфейсы
- Слоистая архитектура
- Go modules
- Backend
- Echo http router или любой другой роутер
- HTTP - GET и POST запросы, HTTP заголовки, cookies
- Базы данных
- PostgreSQL
- Миграции
- Frontend - HTML/CSS, Bootstrap
- Тесты - юнит тестирование, testify
- Деплой - облачный хостинг, командная строка Linux
Мотивация проекта #
- Использование cookies и сессий для авторизации пользователей
- Знакомство с многослойной архитектурой в go и внедрением зависимостей
- Знакомство с Rest
- Работа с внешними API
Функционал приложения #
Работа с пользователями:
- Регистрация
- Авторизация
- Logout
Работа с локациями:
- Поиск
- Добавление в список
- Просмотр списка локаций, для каждой локации отображается название и температура
- Удаление из списка
REST API #
- Архитектурный стиль - REST для всех обработчиков
- Все эндпоинты существуют под общим путём
/api. Пути ниже относительны его, пример -/api/auth/sign-up.
Интерфейс приложения #
Для вёрстки предлагаю пользоваться Bootstrap 5.
Главная страница #
- Заголовок
- Для неавторизованных пользователей - кнопки регистрации и авторизации
- Для авторизованных пользователей - логин текущего пользователя и кнопка Logout
- Контент
- Поле ввода для поиска локации по названию
- Список добавленных локаций. Каждый элемент списка отображает название, текущую температуру и кнопку “удалить”
Страница результатов поиска локаций по названию #
Переход на эту страницу осуществляется в результате заполнения поля ввода на главной странице, либо на странице результатов поиска.
Содержимое:
- Заголовок, такой же как на главной странице
- Поле ввода для поиска по названию - такое же, как на главной странице, чтобы не возвращаться туда для каждого нового поиска
- Список найденных локаций с кнопкой “добавить”. При нажатии на кнопку происходит переход на главную страницу
Остальное #
- Страницы с формами регистрации и авторизации
Работа с сессиями и cookies #
Чтобы реализовать авторизацию пользователя и позволить браузеру “запоминать” авторизован ли текущий пользователь, необходимы сессии и cookies. Подразумевается, что студент имеет общее представление об этих понятиях.
При авторизации пользователя бэкенд приложение создаёт сессию с идентификатором, и устанавливает этот идентификатор в cookies HTTP ответа, которым приложение отвечает на POST запрос формы авторизации. К тому же, сессия содержит в себе ID авторизовавшегося юзера.
Далее, при каждом запросе к любой странице, бэкенд приложение анализирует cookies из запроса и определяет, существует ли сессия для ID из cookies. Если есть - страница рендерится для того пользователя, ID которого соответствует ID сессии из cookies.
База данных #
В этом проекте предлагаю использовать Postgres/MySQL/MariaDB. Создавать таблицы будем не через ручные запросы, а через миграции через goose или go-migrate.
Таблица Users
#
| Колонка | Тип | Комментарий |
|---|---|---|
| ID | int | Айди пользователя, автоинкремент, первичный ключ |
| Login | Varchar | Логин пользователя, username или email |
| Password | Varchar | Хранить пароль в открытом виде небезопасно, лучше использовать шифрование, например BCrypt |
Таблица Locations
#
Локации пользователя, в которых он хочет знать погоду. Одна и та же локация может повторяться для нескольких пользователей.
| Колонка | Тип | Комментарий |
|---|---|---|
| ID | int | Айди локации, автоинкремент, первичный ключ |
| Name | Varchar | Название |
| UserId | int | Пользователь, добавивший эту локацию |
| Latitude | Decimal | Широта локации |
| Longitude | Decimal | Долгота локации |
Таблица Sessions
#
| Колонка | Тип | Комментарий |
|---|---|---|
| ID | UUID | Айди сессии, UUID, первичный ключ |
| UserId | int | Пользователь, для которого сессия создана |
| ExpiresAt | Datetime | Время истечения сессии. Равно времени создания сессии плюс N часов |
Получение информации о погоде с помощью OpenWeatherMap API #
В предыдущих проектах мы писали своё API, в этом будем пользоваться чужим. Нам нужно искать локации по названию и получать погоду для локации.
OpenWeather #
Существует множество сервисов, предоставляющих API с таким функционалом, один из них - https://openweathermap.org/. Я выбрал этот вариант, потому что он позволяет бесплатно совершать 60 запросов в минуту.
Для выполнения запросов к API нужен ключ, для его получения необходимо:
- Зарегистрироваться на https://openweathermap.org/
- Создать бесплатный ключ с лимитом 60 запросов в минуту
- Дождаться активации ключа (придет на указанную Вами почту)
Работа с API #
Документация - https://openweathermap.org/api.
Нам нужно 2 метода API:
- Поиск локаций по названию - https://openweathermap.org/current#geocoding
- Получение погоды по координатам локации - https://openweathermap.org/current#one
Первым делом следует поэкспериментировать с API вручную, чтобы понять как делать запросы, и что приходит в ответе. Сделать это можно через curl, либо воспользоваться отдельным приложением, например https://insomnia.rest/.
Интеграция OpenWeather API с Go приложением #
Шаги:
- Делаем запрос
- Получаем ответ
- Десериализуем ответ в объект
Для работы с API потребуется HTTP Client, будем использовать встроенный в go.
С десериализацией поможет пакет encoding встроенный в std.
Observability #
Для обеспечения прозрачности и отладки поведения приложения важно реализовать наблюдаемость (observability) - способность системы предоставлять достаточно информации для диагностики проблем без необходимости изменять код.
Необходимо реализовать middleware, который логирует каждый входящий HTTP-запрос и ответ. В логах должна содержаться следующая информация:
- time - Время запроса в человекочитаемом формате (например,
2025-11-21T14:32:05.123+03:00) - req - Метод и URI запроса (например, GET /locations), Body, если присутствует в запросе
- resp - тело ответа на запрос
- code - HTTP-статус-код (например, 200, 404, 500)
- duration_ms - Время обработки запроса в миллисекундах (с точностью до двух знаков после запятой)
Фронтенд #
Я считаю, что уметь делать простой фронтенд с нуля - универсально полезный навык для всех разработчиков. Однако, в условиях дефицита времени это может быть нерационально.
Поэтому, для проекта существует готовая верстка, которую можно взять за основу. Если хотите расширить функционал проекта или сделать верстку с нуля - приветствую желание самостоятельно это сделать.
Репозиторий с версткой - https://github.com/zhukovsd/weather-viewer-html-layouts.
Что внутри - макеты четырёх страниц, адаптивная верстка (десктопы, телефоны). Минимальный Javascript для показа меню навигации на телефонах.
Задеплоенные на GitHub Pages страницы для демонстрации:
- Главная страница с 5 карточками погоды
- Результаты поиска
- Авторизация, дополнительный вариант с ошибками валидации
- Регистрация, дополнительный вариант с ошибками валидации
- Страница с ошибкой
Тесты #
Интеграционные тесты сервисов по работе с пользователями и сессиями #
Покроем тестами связку слоя данных с классами-сервисами, отвечающими за пользователей и сессии.
Пример - вызов метода регистрации сервисе, отвечающем за работу с пользователями, должен привести к тому, что в таблице Users появляется новая запись. Если при регистрации автоматически создается сессия, так же можем проверить, что она была создана.
Что ещё стоит проверить тестами:
- Регистрация юзера с неуникальным логином приводит к exception
- Истекание сессии
Детали:
- Для тестов должна использоваться БД, отдельная от основной (in-memory БД или независимая schema в основной БД), которая пересоздается (или очищается) перед каждым тест кейсом
- Понадобится 2 конфигурации приложения - основная (для разработки и деплоя) и для прогона тестов. Конфигурации могут отличаться настройками доступа к БД для Hibernate, настройками приложения (длительность сессии, например)
Интеграционные тесты для сервиса по работе с OpenWeather API #
Покроем тестами связку HTTP клиента и класса-сервиса, который пользуется этим клиентом для получения данных. Для того чтобы не делать настоящие запросы к API во время прогона тестов, следует использовать мок HTTP клиента и его ответов.
Пример - запрашиваем список локаций у сервиса, мок HTTP клиента возвращает заданный в тесте ответ (в виде строки, например), сервис его парсит и возвращает коллекцию объектов-моделей. Проверяем, что коллеция содержит ожидаемую локацию.
Что ещё стоит проверить тестами:
- В случае ошибки (статусы 4xx, 5xx) от OpenWeather API сервис выбрасывает ожидаемый тип исключения
Деплой #
Будем вручную деплоить приложение на удалённый сервере. Шаги:
- В хостинг-провайдере по выбору арендовать облачный сервер на Linux
- Подключиться к серверу через SSH, установить Go и поднять postgresql
- Используя git склонировать проект
- Написать команду для запуска приложения в background режиме, чтобы после отключения от консоли сервера оно продолжало работать
Ожидаемый результат - приложение доступно по адресу http://$server_ip:8080/$app_root_path.
План работы над приложением #
- Создать заготовку Go приложения с разделениям по слоям
- Написать модели сущностей БД -
User,Location,Sessionи миграции для создания таблиц - Написать json контракты для всех обработчиков
- В обработчиках авторизации и регистрации реализовать бизнес логику работы с сессиями, cookies, БД
- Интеграционные тесты для сервиса регистрации
- Написать сервис для работы с OpenWeather API
- Интеграционные тесты для OpenWeather API
- Реализовать бизнес логику приложения - поиск, добавление, удаление локаций, просмотр погоды
- Создать интерфейс главной страницы и страницы поиска локаций
- Деплой
Ресурсы для работы над ошибками #
- Реализации проекта другими студентами и мои ревью этих реализаций
- Чеклист для самопроверки с типовыми ошибками (в конце страницы)
- Присылайте законченные проекты в чат, добавляю их в список, сообщество делает ревью проектов
Чеклист для самопроверки #
❗️Спойлеры: советую не читать этот список до того момента, пока не допишете первую самостоятельную работающую версию проекта❗️
Функциональные проблемы:
- Отсутствие валидации и визуализации ошибок при регистрации и авторизации
- Несовпадение имени локации, по которой мы её добавляли, с тем именем, по которому она отображается, пример:
- Ищем “Санкт-Петербург”, добавляем, сохранив в БД координаты
- Для показа погоды делаем запрос к API по координатам, получаем погоду и имя локации. Найденная по координатам локация называется “Новая Голландия”, а не “Санкт-Петербург”
- Невозможность отличить несколько локаций с одним названием. Пример - в мире есть несколько городов с названием “Гомель”. Возможные решения - показывать координаты локации
- Возможность удалить локацию чужого пользователя через подмену ID в запросе
Проблемы и ошибки в коде:
- Уязвимость к race conditions, подробно на примере разбирал здесь
- Размытые границы между слоями MVCS архитектуры приложения
- Большое количество дубликатов кода, например заголовки и ошибки при http запросах
- Отсутствие логирования, что не позволяет быстро выявлять проблемы в коде
- Сильная связанность компонентов сервиса
- БД и сущности:
- Отсутствие unique индексов на поля или комбинации полей, которые должны быть уникальными. Например - логин пользователя
- Отсутствие внешних ключей между таблицами
- Излишне сложный дизайн хранения локаций пользователей (например, через many-to-many). Подробно эту тему разбирал тут https://www.youtube.com/watch?v=yLBn7qmyCOk
- Отказ от использования интерфейсов, для частей которые будут подмененны на моки
- Недостаточно очевидный нейминг
Общее:
- Трудно локально запустить проект, отсутствие README
- Ключ для OpenWeather API не должен попасть в репозиторий