• ,

Тестовое задание на трудоустройство, давайте разберемся..

Друзья, всем привет.

хочу поделиться с вами опытом решения тестового задания на позицию java developer'а российской компании. Сразу скажу, реализовать основной функционал задания не представляет особой сложности, но как всегда важны детали и мелочи, которые помешали мне сдать его в срок, по заданию мне так ничего и не ответили — вакансия у них уже закрыта была когда я высылал им. Предлагаю разобраться с заданием всё ли я сделал что от меня требовалось. А тем кто понятия не имеет, как его сделать, я добавлю много воды, о том как я с ним разделывался.

Если это вдруг кому интересно — добро пожаловать под кат.

Сразу скажу, что тут всё решение я выкладывать не буду, но за-то будет много объяснений для начинающих, если кому не интересно читать мои изливания, вот вам проект на github

Начну с самого текста задания.


Тестовое задание №1
Описание: Сервер API (JSON HTTP API)
Средства разработки: Java
Framework: Play Framework 2.4 (или выше) или Spring boot 1.2.3 (или выше)
База данных: MySQL
Протокол: HTTP, порт 80
Функционал (запросы):
  1. Загрузчик.
    • Передаем на сервер файл (картинка аватара JPG).
    • Сохраняем картинку в каталоге на сервере.
    • Ответ сервера — внутренний URI картинки.
  2. Добавление нового пользователя.
    • Передаем на сервер персональные данные пользователя (URI картинки, имя пользователя, email и т.д.).
    • Сохраняем информацию в базе данных.
    • Ответ сервера — уникальный ID нового пользователя.
  3. Получение информации о пользователе.
    • Передаем на сервер уникальный ID пользователя.
    • Читаем информацию из базы данных.
    • Ответ сервера — персональные данные пользователя (см. выше).
  4. Изменение статуса пользователя (Online, Offline).
    • Передаем на сервер уникальный ID пользователя и новый статус (Online, Offline).
    • Изменяем статус пользователя.
    • Ответ сервера — уникальный ID пользователя, новый и предыдущий статус.
    Примечание: на сервере выполняется запрос к внешнему API/базе данных. Так как это упрощенное тестовое задание необходимо реализовать «заглушку” с имитацией обращения и задержкой по времени 5-10 сек.
  5. Статистика сервера.
    • Передаем параметры на сервер: 1. статус клиентов (Online, Offline или отсутствует), 2. уникальный ID (timestamp) запроса (может отсутствовать)
    • Ответ сервера — список пользователей со статусами и URI картинки, а также уникальный ID (timestamp) запроса.

    Примечание: Если в запросе есть параметры, то сервер должен фильтровать по ним свой ответ. Если в запросе есть уникальный ID (timestamp) запроса (полученный ранее), то сервер должен вернуть только пользователей, у которых изменились статусы после (по времени)этого уникального ID (timestamp).

Обязательные требования:
— RESTful.
— Все данные в формате JSON.
— Сервер API должен быть спроектирован с учетом того, что запросы 3 и 5 имеет высший приоритет (по отношению к запросам 1, 2, 4) и должны быть выполнены максимально быстро.
— Обработка ошибок.
Необязательные требования (желательно):
— Документирование кода.
— Архитектура Сервера API должна быть рассчитана на высокую нагрузку и масштабирование.
— Тесты.
Результат тестового задания:
— Результат тестового задания должен быть предоставлен в архиве и с подробной инструкцией по его развертыванию. Желательно приложить Dockerfile для сборки Docker контейнера для тестового задания. Можно загрузить на github.com.
— Должен содержать краткую документацию созданного API (список запросов, параметры запросов, форматы запросов, форматы ответов и т.д.).
— Информация о времени потраченном на тестовое задание в разрезе: проектирование, программирование, документация и т.д.

Обращаем внимание, что это тестовое задание предназначено только для оценки знаний и умений, а не ставит целью создание законченного продукта (сервера API), поэтому допускаются упрощения с объяснением и указанием причин.



внимательные и опытные программисты могут пропустить следующий раздел, тут я буду разбираться с самим текстом задания.

»Шапка" задания не вызывает никаких сложностей с восприятием, поэтому просто скажу что мой выбор пал на Spring Boot, но не потому что я уже когда-то что-то на нем делал, а потому что я уже прошел реальный проект с использованием Spring (но Boot'а там не было, как я понимаю из-за его простоты).
По функционалу сервера:
1) Загрузчик файлов. Тут принципиально ничего сложного нет, мне нужно было просто разобраться как картинки вообще хранятся на сервере, оказалось, что наиболее удобным способом является простое размещение их в какой-нибудь специально отведенной для этого директории. Конкретную реализацию разберем ниже.
2) Добавление нового пользователя, простая операция, если вы делали когда-нибудь CRUD приложения, то он поддержит меня, если нет — всё увидите ниже.
3)Получение информации о пользователе. нет вопросов — всё ясно.
4)Изменение статуса пользователя. первые два пункта задания ясны как день, а что там с внешним запросом??? тут без 100гр не разобраться, я даже сейчас на 100% не уверен правильно ли я понял. Детали ниже.
5)Статистика сервера. Тут тоже интересно. первым пунктом предлагается реализовать метод с различными вариантами параметров, пока не понятно как это делать учитывая что это должен быть метод контроллера. вторым пунктом спрашивают всех юзеров, у кого изменился статус после момента времени вроде понятно, но есть тонкости.

Getting Started
ох, сколько раз я читал эту фразу, пока разбирался с этим заданием!

Если вы пробовали когда нибудь разбираться в настройке проекта на Spring, но при этом по какой-то причине так ни разу и не попробовали Spring Boot, поздравляю вас, вы испытаете просто восторг от того, что я напишу ниже. Я где-то вычитал, что раньше программисты очень большое количество кода раньше переносили из проекта в проект, это шаблонный код — настройки подключения к базам данных, маппинг сервлетов и прочее-прочее, так вот чтобы, например, уменьшить объем шаблонного кода для работы с базами данных мы используем JPA/Hibernate, они скрывают часть шаблонов но чтобы настроить их опять же нужно писать xml файл или конфигурационные классы. а если у вас маленький проект, то получается что ни фига вы не меньше кода пишете, а даже наоборот. Дальше мы оборачиваем работу с JPA в Спринг, есть много проектов, но наиболее удобный это, конечно же, Spring Data. Это очень большой проект который может работать наверное со всем чем можно и JPA и NoSQL еще целую кучу разных проектов, он невероятно магический мы будем его использовать в нашем проекте.
Используя Spring мы почти избавляемся от настроек соединения с БД, Spring все делает за нас, нам только нужно навтыкать нужных аннотаций по транзакционности, кешированию и в особых случаях нагуглить (подсмотреть у других) еще каких-нибудь настроек в кофигурации контекста. Но при этом у большинства начинающих разработчиков нет абсолютно никакого понятия как создать проект на Spring. Никто не знает полностью как его настроить чтобы запустить проект и получить результат в браузере пройдя по ссылке начинающейся с localhost:8080/*. И тут на сцену выходит Spring Boot!

Про Spring Boot лучше рассказать на конкретном примере!
Начнем с заготовки.
Чтобы создать проект Spring Boot разработчики Spring придумали «конструктор» создания шаблонов. Им можно воспользоваться на их сайте, но гораздо проще сделать это в нашей любимой IDE Intellij IDEA. И так:
File->New->Project
В окне переходим на вкладку Spring Initializr, в ней должно быть выставлено jdk, и URL start.spring.io, проверяем подключение к интернету, далее нужно будет выбрать название, а затем технологии которые мы будем использовать, на первом этапе нам нужно только WEB — ставим рядом с ней галочку и далее создается проект.
Чтобы мавен подтянул все зависимости нам нужно открыть вкладку Maven в идее и нажать кнопку обновить. Мы получили готовый шаблон приложения, в котором зарыты все настройки для клиент-серверного общения. Чтобы получить первое впечатление создадим класс контроллера(про MVC-то уж наверняка все слышали). Во всех Spring приложениях контроллеры имеют достаточно простую конструкцию — это класс, который помечен аннотацией @Controller(возможны префиксы, например, @RestController), этот класс отвечает за обработку входящих запросов. Для того, чтобы контроллер распознал запрос на какой-нибудь адрес нужно сделать маппинг этого адреса на метод контроллера.
Ниже представлен код этого класса, пока мы будем работать без иехрархии директорий, поэтому положим его в один пакадж с классом созданным автоматически Initializr'ом, у меня он называется DemoApplication.

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/hello")
public class DemoController {

    @RequestMapping(method = RequestMethod.GET)
    public String halloWorld() {
        return "Hello World!";
    }

    @RequestMapping(value = "/{name}", method = RequestMethod.GET)
    public String halloName(@PathVariable("name") String name) {
        return "Hello, " + name + "!";
    }
}


разберемся что тут.
@RestController. как раз та аннотация, о которой я писал выше. именно рестконтроллер используем поскольку мы хотим сразу увидеть результат и не хотим писать страницы.jsp (фу-кака), нам будет проще сразу увидеть результат в браузере в виде строки.
@RequestMapping — как раз привязка к адресу. префикс общего адреса будет такой: localhost:8080.
Как мы видим весь класс висит на адресе /hello, это означает что все методы внутри этого класса имеют префикс localhost:8080/hello.
Далее первый метод класса, в его собственном маппинге указан метод Http протокола — запрос GET(про методы Http протокола почитайте сами)
Что это всё означает? обратившись GET запросом на адрес localhost:8080/hello — получим ответ в виде строки «Hello World!», давайте проверим это!

В классе DemoApplication, есть одна крутая аннотация, которая можно сказать в одиночку запускает весь контекст спринга — @SpringBootApplication. Метод main этого класса становится волшебным, как раз запускает всю магию скрытую в SpringApplication, если вызвать контекстное меню на этом классе то в строке Run появятся варианты, рекомендую запускаться раном с зеленой меткой, так консоль будет приятнее выглядеть и в будущем будет проще читать логи прям из нее.
Запускаем приложение.
когда вывод в консоль прекратится, вы должны увидеть в консоли
2015-09-02 09:25:36.895 INFO 5844 — [ main] s.b.c.e.t.TomcatEmbeddedServletContainer: Tomcat started on port(s): 8080 (http)
2015-09-02 09:25:36.900 INFO 5844 — [ main] demo.DemoApplication: Started DemoApplication in **** seconds (JVM running for 15.501)
где "****" — длительность запуска приложения :)
после этого в любом браузере (или curl, или чем там вы пользуетесь?) нужно набрать адрес на который замапили метод контроллера
localhost:8080/hello
В браузере должно отобразиться каноническое
Hello World!
вот вам и веб приложение!

Если вы заметили в контроллере есть еще один метод, там есть собственный маппинг адреса, к текущему адресу добавляется плейсхолдер. Который спрингом передается в метод в качестве параметра. Не трудно догадаться что за это отвечает аннотация @PathVariable.
Так на запрос
localhost:8080/hello/Ваше имя
браузер покажет
Hello, Ваше имя!

С основами Spring Boot разобрались.
Далее прикрутим базу данных, но это будет уже в следующем посте.
Всем спасибо.

10 комментариев

timurnav
Прочитал задание еще раз и понял что я кое в чем накосячил! Блин 10 раз уже наверно это задание читал и всё равно блин нашел что-то новое…
quazrckk
Какой срок на выполнение давали?
timurnav
мне сроки не ставили, даже наоборот, у меня спросили за сколько я справлюсь. мне задание прислали в понедельник, в следующий понедельник вакансия уже была закрыта
4e4el
Статья понравилась, жду продолжения :)
Kaxeda
А это на какую позицию было такое тестовое задание? На джуна или на миддла? Потому что у меня на джуна было в разы проще…
timurnav
Java разработчик написано, без уточнений
lichMax
это же обычно «миддл» означает. Что вообще-то видно по заданию.
timurnav
  • timurnav
  • +2
  • Комментарий отредактирован 2015-09-02 19:11:55 пользователем timurnav
Там еще прикол такой был, я им пару вопросов уточняющих задал, которые мне казались спорными, ответ последовал такой: я не могу полностью ответить на этот вопрос, иначе это будет считаться подсказкой :D
Это такой тонкий референс к «неточным» формулировкам задач на javarush
quazrckk
однако на jr валидатор принимает единственный вариант, а людям все же можно объяснить, почему сделал именно так)
timurnav
  • timurnav
  • 0
  • Комментарий отредактирован 2015-09-02 22:04:18 пользователем timurnav
согласен, в качестве учебного пособия пару раз на этом можно заострить внимание) я тоже очень много ругался на тему того, что «я не понял задание», меня Хуберт даже в игнор в вк ставил помнится) но щас я понимаю, что с его колокольни такие банальные вещи понять сложно, все равно что тебя попросят объяснять таблицу умножения :)

ps не единственный)
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.