Финальное задание по курсу https://golangcourse.ru/
Можно делать в свом репозитории, можно и нужно выложить (только его! не решения домашек) на гитхаб.
Проект значительно больше домашки и предполагает построение полноценного веб-приложения.
Требования по проекту:
- В качестве структуры проекта надо взять https://github.com/golang-standards/project-layout. Не забудьте посмотреть рекомендуемые там видео.
- Для управления зависимостиями используйте go modules (новая фича, в курсе не было, гуглить по golang modules), хотя вендоринг при помощи dep тоже можно.
- Для проекта должен быть настроен golang-ci ( https://github.com/golangci/golangci-lint ) и выбранные вами проверки оттуда должны проходить. Большинство этих проверко подствечивается вам в VS Code или JetBrains GoLand
- В проекте должны быть тесты. Максимально возможный %. Тесты не должны чисто быть для покрытия, они должны реально проверять что тестируемая логика работает.
- (опционально) Если у вас много методов, который отвечают JSON-ом - иметь доку по методам в формате swagger.
- (опционально) Если у вас много методов, который отвечают JSON-ом - генерировать сервер через swagger-доку, те подход schema-first.
- (опционально) Статика встроена прямо в бинарь ( те вообще нет зависимостей ) через https://github.com/rakyll/statik или подобные библиотеки
Задания сознательно даются с неполным описанием, там надо додумать, допроектировать ( это тоже часть задания! ), задать вопросы.
Проект "Грейдер".
Это достаточно большое задание, в котором еще необходимо будет написать фронтенд. Но зато вы попробуете почти все что модно.
Это примерно то, чем людит пользуются при отправки решения домашек на разных образовательных платформах.
В качестве фронтенда возьмите фреймворк bootstrap (https://getbootstrap.com/), аякса не надо, все через полную перезагрузк страниц, JS тоже никакого не нужно. Я на нем делал формы в лекции с базами.
Суть заключается в следующем:
- Есть страница с заданием (прямой переход по ссылке), там есть текст и форма загрузки файла.
- Пользователь грузить файл, он добавляется в базу с пометкой "обрабатыватся" и в очередь на обработку.
- Очередь (в виде отдельного сервиса) достает задачу на проверку и отправляет ее во внешний веб-сервис по описанному проктолу
- Этот сервис выполняет задачу в докер-контейнере, и возвращает, соответственно, результат
- Пользователь видит результат и дополнительное сообщение, если оно есть (например детали с ошибкой)
Будут следующие страницы (можно отклоняться, но не в стороту потери фич):
Пользовательская часть:
- Авторизация
- Регистрация
- Список заданий, куда были загрузки
- Страница задания, куда были загрузки - отображение списка загрузок с результатом
- (опционально) Профиль пользователя
- (опционально) Смена пароля
Админка:
- Список заданий
- Страница создания/редактирования задания ( там же - получение ссылки на задание )
- Страница результатов задания - каким пользователем какое было выполнено - https://s.mail.ru/Fara/zjdHbywJo
- (опционально) Много админов и задания принадлежат конкретному админу
- (опционально) Список пользователей
- (опционально) Назначение пользователя админом
Очередь:
- Простой разгребатор очереди, который берет задание и отправляет во внешнюю систему
Внешний грейдер:
- API, который принимает решение на проверку от основного сервера
Грейдерная часть основного сервера:
- Метод, который принимает результат проверки задания (см выше)
Механика работы грейдера на сервере:
СЛОЖНЫЙ ВАРИАНТ ( ниже будет простой ):
Грейдер в задание настраивается при помощи следующего json-а в простом текстовом фоле ( или нескольких полей ) и валидацией на сервере.
{
// адрес внешнего грейдера, куда будут отправлять на проверку задания
// можно сделать через настройку в отдельном интерфейсе и указывать только имя а не урл каждый раз
"external_grader": "https://127.0.0.1:8021/api/v1/grader",
// список файлов, которые должен загрузить пользователь
// обязательно проверять имя файла
"files": [
{
"label": "hw1_game/main.go",
"filename": "main.go"
}
],
// это дополнительная информация, которая уходит в грейдер
"grader_payload": {
// имя контейнера, который нужно запустить
"container": "golangcourse_final",
// в контейнер надо как опцию передать параметр partId с таким значением
"partId": "HW1_game"
}
}
Пример команды, которая должна вызваться при проверке:
docker run --user 1000 --network none --rm --name runXXXXXXXXXXXX \
-v $FILE_PATH:/shared/submission/ \
$CONTAINER partId $PART_ID
$FILE_PATH
- пусть у вас на диске с файлом решения, который монтируется в контейнер как volume$CONTAINER
- параметрgrader_payload.container
из json выше$PART_ID
- параметрgrader_payload.partId
из json выше- Еще добавьте параметр с таймаутом
- Эти параметры подставлять напрямую, а не через ENV - переменные через $ тут чтобы показать что сюда надо подставить что-то
Таким образом я могу одним контейнером проверять несколько заданий ( как и происходит в реальности ).
Запуск вышеописанной должен закончится с кодом 0 и следующим результатом:
// успешное выполнение
{
"pass": true,
"text": "Поздравляем! Вы успешно сделлаи задание"
}
// неуспешное выполнение
{
"pass": false,
"text": "FAIL\ncompilation error\n\nSTDOUT:\nFAIL coursera/__grader/assigments/hw1_game [build failed]\n\n\nSTDERR:\n\n# coursera/__grader/assigments/hw1_game [coursera/__grader/assigments/hw1_game.test]\nassigments/hw1_game/main.go:82:1: syntax error: non-declaration statement outside function body\n"
}
text
- выводится пользователюpass
- решение принято или нет
Если контейнер завершается с ненулевым кодом - значит FAIL + Internal error в описании
ПРОСТОЙ ВАРИАНТ №1:
Запускаем что-то в докере, который должен вернуть статус 0 ( успешно ) или не 0 ( фейл ). Например это может быть запуск тестов. Но в этом случае надо предусмотреть загрузку сопуствтующих файлов ( или иметь их в контейнере )
ПРОСТОЙ ВАРИАНТ №2:
Запускаем код пользователя в докере, на STDIN подаем туда что-то, сравниваем результат с эталоном в ситеме, если сошлось - ОК, нет - фейл.
Как только контейнер отработал - есть 2 варианта:
- С оффлайн разгребальщика ( который отдельный сервис) соединение висит Х минут ( таймаут в докере ), после чего обрывается с FAIL + timeout по решению. Если все ок - в решение проставляется соответствующий статус
- В грейдер приходит callback-url, который надо дернуть по завершению проверки решения. Не зубадьте тут авторизаци в виде какого-то JWT-токена с ограничением времени жизни
Общая схема работы: https://s.mail.ru/ALmB/g5EJe6GkB ( https://sequencediagram.org/ )
title Грейдер
User->Server: Загрузка решения
note right of Server: Сохраняем в базу
Server->QueueProcessor: RabbitMQ
QueueProcessor->Grader: HTTP с файлом и параметрами
Grader->Docker: Запуск контейнера
Docker->Grader: JSON c результатом
Grader->QueueProcessor: #1 HTTP ответ
Grader->Server: #2 через Callback-URL
Server->User: Результат решения
Эта последовательность разработки немного упростит вам работу:
- начните с захардкоженным юзером
- сделайте юзерскую часть интерфейса - пусть там из базы что-то фейковое берется на заранее добавленных данных
- сделайте сервис очереди
- сделайте сервис грейдера - пусть отдает фейковые данные
- сделайте сервис грейдера чтобы работал полноценно с докером
- сделайте работу с юзерами ( рега, авторизация, админы )
Бинарников у вас будет 3:
- Сервер куда ходят юзеры и админы
- Разгребатор очереди
- Грейдер
Совсем опциональные фичи:
- Вход через oauth ( мейл, ВК, фейсбук) - https://github.com/golang/oauth2 - см в 3-й части курса
- Несколько языков программирования в грейдере