Когда заходишь на Postcrossing, то осознаёшь, насколько много людей может сойти с ума по одной простой идее. Именно подкупающая простота идеи стала основным фактором, почему я взялся за реализацию одного специфичного кроссинга со своими дамами и преферансом объектами обмена и дополнительным функционалом.


Идея


Шёл январь 2017 года. Как-то раз один человек в беседе со мною упомянул, что хотел бы реализовать идею обмена фенечками (так называемые «браслеты дружбы») по принципу Посткроссинга: пользователь заходит на сайт, жмёт кнопку — получает адрес, на который он должен отправить сплетённую им фенечку, после чего он сам начинает получать фенечки от других пользователей в ответ (и так по кругу). Желательно, чтобы обмен был возможен между разными странами.

Тогда у меня уже было около 5 лет опыта разработки веб-приложений на Java и чуть-чуть на PHP. Когда мне описывали функционал, необходимый для работы желаемого кроссинга, я думал использовать мои старые наработки на PHP и сделать проект примерно за две недели или масимум за месяц. Но, как это часто бывает, мои оценки оказались слишком оптимистичными. Уже во время перечитывания исходного кода своего шаблонизатора на PHP я испугался написанного понял, что эти старые университетские поделки требуют немедленной отправки в утиль как минимум хорошей оптимизации.

Чем больше я погружался в свои старые исходники на PHP, тем больше понимал, что с моим уровнем владения этим языком и состоянием исходников даже этот небольшой проект делать мне будет больно. Поэтому я всё-таки решил использовать Spring Framework, ставший для меня тёплым и мягким мохом за годы работы с Java Web. Я не хотел делать этого изначально ввиду казавшейся немасштабности задуманного. Конечно же, я ошибался, поэтому решение использовать привычный язык Java превратило потенциальную боль в последующее удовольствие.

Проектирование


Так как идея была не моя, во время проектирования я часто и много дёргал автора идеи, чтобы быть уверенным в том, что моё видение проекта соответствует авторской задумке. В процессе длительных обсуждений я проникся проектом и уже сам стал вводить различные удобные и полезные плюшки для пользователей и админов. Проектированию было отдано должное внимание: мы обсуждали каждую деталь функционала, рисовали макеты почти всех страниц, вели документацию с описанием логики работы приложения и пользовательским функционалом.

Макеты и документация постоянно эволюционировали, обрастая деталями и новым функционалом. Впоследствии макеты стали одной из тех вещей, который показали, насколько я ошибся с оценкой сложности проекта.


Пример эволюции макета страницы редактора урока

Конечно, я сам виноват в последующем усложнении функционала, увеличив тем самым время разработки. Однако, я не хотел, чтобы в Интернете появился очередной говносайт, коих тысячи некачественный сайт; мне хотелось сделать сайт приятным для пользователей как в плане удобства использования, так и в визуальном плане. Более того, перфекционизм во мне просто не позволял пользоваться методом «херак-херак — и в продакшен». Поэтому даже макеты страниц мы прорабатывали со скурпулёзностью (и вы ещё увидите, что это было далеко не пределом замороченности над проработкой деталей).


Макет страницы настроек профиля пользователя

Дойдя до опционального функционала, которым я тогда планировал пользоваться в одиночку, макеты я стал рисовать от руки на графическом планшете.


Макет страницы «Пользователи» в админке

Во время проектирования мы старались продумать систему так, чтобы после запуска наше дальнейшее вовлечение в жизнь сайта было минимальным; нам очень не хотелось заниматься модерацией контента, публикуемого на сайте пользователями, а уж тем более нанимать модераторов. Мы хотели, чтобы сообщество само решало, какой урок публиковать, какую фенечку не допускать на публику, какого пользователя забанить за непристойный профиль или комментарии. Конечно, полностью от этого избавиться не получилось, но в итоге нам удалось по меньшей мере разгрузить себя от потенциальной рутины.

В конце этапа проектирования я понял, что работы мы насочиняли минимум на полгода. Более того, кроме самой возможности обмена фенечками мы решили добавить возможность написания статей по плетению фенечек (а позже — и по рукоделию в целом) и проверки этих статей самим сообществом (впоследствии эта фича оказалась даже более трудной и долгой в реализации, чем система обмена). Но в целом мне нравилось, как это работало «на бумаге» и выглядело на макетах, поэтому я приступил к реализации и решил довести её до конца.

Итак, по нашей задумке приложение должно было обладать следующим основным функционалом:

— регистрация на сайте и настройка аккаунта;
— организация обмена фенечками с людьми по всему миру;
— написание уроков, проверка и одобрение их сообществом сайта;
— страницы с конкретным уроком и фенечкой;
— страницы со списком уроков и фенечек;
— комментирование уроков и фенечек;
— система оповещений о событиях;
— система жалоб, банов и квот;
— чат между пользователями;
— статистика сайта;
— админка.

Кроме того, сайт изначально должен быть ориентирован на международную аудиторию: обмен можно осуществлять по всему миру, уроки можно писать на любом языке, а сам сайт можно локализировать на любой язык (конечно же, если найдутся переводчики, желающие этим заняться; для этого позже я даже написал небольшое приложение, с помощью которого можно удобно делать перевод сайта на язык носителя).

Cайт было решено создать с минимальными финансовыми вложениями: никаких покупок или подписок на сторонние сервисы, никаких вложений в рекламу, наёма переводчиков и оплаты SSL-сертификата. Всё, на что мы позволили себе потратить деньги, были доменное имя и VDS. И немного ниток мулине, но об этом попозже.

Предвкушая грядущее удовольствие от разработки проекта, я приступил непосредственно к этапу реализации. И работать я решил фултайм (да, так тоже бывает и даже с некоммерческими проектами).

Реализация


В нашей команде из двух человек программистом был только я, поэтому большая часть повествования о реализации будет от моего имени.

Первым делом я набросал модель базы данных. После первых итераций у меня получилось около 30 таблиц. В финальной же версии их было уже 43. В качестве ORM я выбрал Hibernate, так как имел небольшой, но вполне приятный опыт работы с ней. В качестве самой базы выбрал MySQL.


Модель базы данных, вид издалека

Уже в самом начале я задумался над тем, каким образом я буду наполнять базу исходными данными. А данных этих было довольно много: языки, страны, статический контент, категории уроков и многое другое. Мне не хотелось хранить и тем более поддерживать данные в формате SQL, поэтому я решил написать свою систему импорта и экспорта данных в CSV-подобном формате. И в последующем не пожалел, так как эти система в разы ускорила процесс разработки и сделала удобным работу с данными на продакшене. Систему я назвал Inida (от «Initial Data», хотя это название уже не очень и подходит).

Подробнее об Inida
Вот пример данных в формате inida:

i Country;isocode;name
;by;Belarus
;gb;United Kingdom
;ru;Russia

Первая строка здесь — заголовок, описывающий операцию (i — insert), тип данных (название сущности в БД) и поля. Строки, начинающиеся точкой с запятой — это сами данные. Если вышеупомянутый текст (Inida-данные) скормить Inida-импортеру, то в базу данных добавятся три записи (в данном примере — три страны). Inida-данные могут иметь несколько заголовков, что позволяет работать сразу с несколькими сущностями БД.

Inida поддерживает следующие операции с записями:

i — insert: добавление новой записи;
u — update: обновление уже существующей записи (по ключам, в качестве которых выступают значения полей, помеченных знаком "+");
m — merge: по сути это insert + update (если записи с указанными значениями ключей нету, то она создаётся; иначе запись обновляется);
d — delete: удаление данных по указанным ключам.

Кроме того, Inida имеет следующие фичи:

  • преобразование значений полей кастомными трансформерами (например, преобразование даты из одного формата в другой, декорирование данных, санитизация и т.д.);
  • алиасы записей для использования этих записей в последующих Inida-данных;
  • batch mode: update, merge или delete записей, удовлетворяющих лишь части ключа (в обычном режиме при попытке обновления сразу нескольких записей, удовлетворяющих одному и тому же ключу, генерируется исключение);
  • подстановки (можно и вложенные): эдакой аналог define в C++ (полезен, когда длинная фраза используется более одного раза);
  • значения по умолчанию (удобно, когда надо продублировать одно и то же значение какого-либо поля для большого количества записей);
  • поддержка значений-списков (если значение поля является коллекцией-списком);
  • поддержка полей-объектов (один уровень вложенности).

А также некоторые другие менее важные, но тоже полезные фичи.

Также Inida умеет и экспортировать данные. Делается это написанием того же заголовка, что импользуется при импорте, только без указания операции:

Country;isocode;name

В вышенаписанном примере экспортер выгрузит все страны в формате inida. Объём выгрузки можно ограничить, указав значения ключа, которому будут удовлетворять выгружаемые записи:

Country;isocode=ru;name


Одна из фич сайта — отображение города пользователя на карте в профиле. Так как я хотел, чтобы наш сайт был максимально самодостаточным в плане зависимости от внешних сервисов, я довольно сильно заморочился ради этой маленькой фичи, потратив массу времени на поиск решений и разработку дополнительных приложений.

Квест о реализации фичи с картами
Во-первых, надо было откуда-то взять сами карты, а точнее тайлы карты Земли разных уровней детализации (чтобы карту можно было приближать). Поиски привели меня к картографическому сервису ESRI, который имел необходимые мне тайлы в свободном доступе. Но проблема была в том, что ссылок на архивы тайлов у них не было (или я их просто не нашёл). Зато каждый тайл можно было скачать по URL'у определённого формата. Я быстро написал приложение, которое выгружало тайлы под нужное количество уровней детализации и раскладывало их по соответствующим папкам. Мне было достаточно всего 8 уровней (0-7) суммарным объёмом около 138 МБ.

Во-вторых, надо было как-то отображать скачанные карты. Благо, этот вопрос решился довольно просто: я нашёл JavaScript-плагин Leaflet, который отлично справлялся с отображением тайлов карты по заданному URL.

В-третьих, наконец-то, откуда-то надо было раздобыть геолокации (координаты и названия населённых пунктов) и желательно на разных языках. Эта часть задачи была самой объёмной и трудной, так как мне не сразу удалось найти более-менее исчерпывающий список населённых пунктов для разных стран. В итоге такой список я всё же нашёл, но название он содержал лишь на одном языке; я же хотел для каждой страны предоставить возможность искать населённые пункты на языках, официальных в этой стране (например, на русском и белорусском для Беларуси), а также на де-факто интернациональном (к сожалению, это английский) и на эсперанто (просто потому что мне очень нравится идея этого языка).

Как следствие, появилась ещё одна подзадача: найти и подготовить списки официальных языков для каждой из стран. К счастью, эту информацию кто-то уже собрал до меня, поэтому мне осталось лишь скормить её программе, которую я опишу парой абзацами ниже.

Источником локализированных имён населённых пунктов стал Nominatim. Так как получить локализированные имена по единственной ссылке не получилось, пришлось делать это в два прохода:

  • запросить информацию о населённом пункте, используя его имя (на доступном языке) и страну; эта информация приходила в формате JSON;
  • используя значение поля «place_id» из полученного JSON, запросить страницу с информацией о населенном пункте и его названиях на разных языках (в виде обычной веб-страницы).

Так как далеко не на все HTTP-запросы успешно приходили ответы (по разными причинам, среди которые была банальная «пропало соединение с Интернетом»), также надо было сделать логирование неуспешных попыток локализации населённых пунктов, чтобы повторить попытку на следующей итерации. Ещё одной трудностью было то, что официально Nominatim запрещено использовать слишком часто, поэтому, чтобы меня не забанили, надо было ввести задержку между запросами, тем самым ограничив их количество в секудну до максимально дозволеного.

В итоге, учтя всё вышеперечисленное, я написал программу, которая занимается локализацией населённых пунктов на языки, официальные в стране, к которой относится локализируемый населённый пункт (а также на английском и эсперанто, как я уже упоминал выше). Локализировать удалось за 4 итерации, а программа работала сутками на протяжении примерно 1.5 месяца на специально выделенном под эту задачу нетбуке.

Очередной задачкой, придуманной себе самому, была подготовка иконок флажков. Проблемы были как с флажками для стран, так и для языков. Даже после того, как я скачал большой свободно распространяемый архив хороших иконок в разрешениях на любой вкус. Пришлось полистать Википедию да пофотошопить.

Квест об иконках для флажков
Первой проблемой, как уже некоторые могли догадаться, оказалось банальное отсутствие флажков для некоторых стран. Дело в том, что я старался поддерживать все официально присвоенные коды стран из ISO 3166-1 alpha-2. Флаги нужных мне стран я находил в основном в Википедии, а затем подготавливал их в Фотошопе в нужном мне разрешении и с эффектом объёма. Мне не хватало порядка десятка флажков, поэтому эту часть задачи я решил довольно быстро.

Более сложной оказалась ситуация с флажками для языков. Дело в том, что на многих сайтах твердили, что использовать флажок для языка не очень рационально ввиду того, что это часто подразумевает к привязке языка к конкретной стране. Например, какой флаг должен быть для английского: американский, великобританский или австралийский? А, быть может, новозеландский? А для испанского: мексиканский или испанский? Аргументы за избегание использования флагов для языков были для меня убедительны, однако мне очень хотелось визуальной репрезентативности, поэтому я всё-таки решил их использовать на нашем сайте (хоть и немного «через себя»).

Основная сложность была в том, что флажки у меня были только для стран. Для языков я их использовать не мог попросту из-за того, что ISO-коды стран и ISO-коды соответствующих им (в частных случаях) языков в общем случае различались (например: BE — белорусский, BY — Беларусь; UK — украинский, UA — украина). Вспомнив, что у меня уже есть списки языков по странам, я решил написать маленькую программку, которая на основании этих данных составит списки стран по языкам (то есть выдаст обратный маппинг).

После того, как программа была написана, а нужные мне списки языков получены, я принялся подготавливать картинки с флажками для языков (просто переименовывал флажки для стран). Подобно случаю со странами, в случае с языками я старался поддерживать весь ISO-639-1. Да, флажков для языков тоже не хватило, но на этот раз в недостача была в несколько раз больше. Долго я разбирался, какой язык в каком регионе используется, но в итоге для каждого из них был подготовлен соответствующий (или не очень, не знаю; по крайней мере пока никто не жаловался) флажок.

Одной из частей функционала сайта, обеспечивающего его регулируемость самим сообществом, стала сситема жалоб и банов. Пользователи могут жаловаться на информацию в профиле (имя, аватар, информация о пользователе), комментарии, советы, почтовый адрес и загружаемые изображения. При наборе достаточного количества жалоб соответствующий функционал блокируется для обжалованного пользователя. Первая блокировка снимается через неделю, а срок каждой последующей блокировки увеличивается вдвое. Некоторые блокировки (например, имени пользователя) бессрочны; это один из тех случаев, где внимание админов всё ещё необходимо.

Ещё во время проектирования я понимал, что на сайт могут быть натравлены боты. Конечно, такой сценарий маловероятен при низкой популярности сайта и его целевой аудитории, но желание сделать всё «по совести» брало верх. Для предотвращения потенциального спама я ввёл систему квот на действия пользователей, так или иначе связанных с генерацией контента на сайте: комментирование, написание сообщений в чате, написание уроков, запрос адресов, регистрация аккаунтов (как с одного IP-адреса, так и суммарно в сутки) и некоторые другие.

Интересной задачей была реализация одной из админских фич, а именно — поиска похожих адресов. Подход я подсмотрел в одной из статей на Хабре (к сожалению, я не смог найти ту статью, поэтому ниже вкратце опишу суть подхода). Честно говоря, я не очень доволен результатом реализации этай фичи из-за относительно большого количества ложных срабатываний.

Как работает проверка похожих адресов
Сначала формируется база сигнатур почтовых адресов. В данном случае сигнатура — это список символов и количество вхождений каждого символа. Делается это так:

0) берём почтовый адрес в виде текста;

1) убираем из адреса ненужную информацию (пробелы, знаки препинания) и переводим все символы в одинаковый регистр;

2) вычисляем сигнатуру оставшегося текста.

Далее, когда регистрируется новый пользователь или когда уже существующий пользователь изменяет один из своих адресов (обычный или на транслите), этот новый (или обновлённый) адрес подвергается вышеописанной процедуре, после чего вычисляется разность сигнатур (сумма разностей между количествами соответствующих букв). Если разность меньше некоторого указанного порога, то адреса считаются похожими.

Пока я усердно работал на серверной частью приложения, мой соавтор не сидел со сложенными руками, а работал над разметкой сайта, используя библиотеку Bootstrap, так как ему очень хотелось, чтобы сайт был удобен в использовании также и с мобильных устройств.
Подготовленные нами макеты страниц здесь очень пригодились.

Дизайн


Я не люблю, когда разметка плывёт, сайт выглядит плохо, а скрипты корректно работают только при выполнении ожидаемых от пользователя действий. Даже во время разработки. Поэтому я всегда старался поддерживать аккуратность внешнего вида сайта, чтобы мне было приятно с ним работать, видеть в этом дизайне новые фичи, создавать для него новые страницы. Я позволял себе тратить время на вылизывание мелочей даже зная, что они всё равно не попадут в финальную версию. Просто потому что сайт ждал тотальный редизайн.


Так выглядел сайт перед началом редизайна

При продумывании внешнего вида финальной версии сайта я учитывал следующие факторы и пожелания самому себе:

  1. дизайн должен соответствовать тематике сайта (фенечки, рукоделие);
  2. дизайн должен создавать тёплую ламповую атмосферу;
  3. дизайн должен делать сайт понятным и удобным в использовании.

Я даже не задумывался над выбором подхода к оформлению сайта, так как он для меня был однозначен: скевоморфизм. Вышеописанные пункты меня ещё больше убеждали в правильности моего выбора. Я прекрасно понимал, насколько этот подход трудозатратен, но я был готов тратить на это время, так как хотел приятного глазу результата. Более того, годы опыта работы в Photoshop облегчили этот процесс и сделали его менее напряжным.

Почему я ненавижу современный дизайн
Решение использовать скевоморфный дизайн было принято также из-за моей нелюбви к современному плоскому дизайну. Лично для меня скевоморфный дизайн является эталонным в плане визуальной понятности и репрезентативности. Дизайн инструментов в Guitar Rig 5, многих плагинов к FL Studio и Kontakt, внешний вид пользовательского интерфейса в игре Hearthstone — всё это безумно радует мой глаз и делает общение с приложением или игрой очень приятным. Подобный дизайн не оставляет вопросов о том, где какой элемент интерфейса и как его использовать.

Нынешние же тренды сделали приложения и сайты сплошь одинаковыми и безликими. Если раньше веб-дизайнеры старались придать сайту индивидуальность, то теперь каждый второй сайт выглядит чересчур шаблонно:

  • огромная картинка или даже видео в качестве фона;
  • абстрактный слоган, написанный огромным шрифтом;
  • излишне широкие фиксированные хедеры;
  • огромный футер на весь экран (а порой и больше);
  • текст, написанный тонкими шрифтами, да ещё и серым цветом на белом фоне;
  • конских размеров отступы по всей странице;
  • кричащие и совершенно не сочетающиеся друг с другом цвета;
  • страница в стиле «пролистай меня вниз и не пойми, в чём смысл рекламируемых здесь сервисов».

Ну и, конечно же, всеми любимые

  • всплывающее окно с информацией про cookies;
  • всплывающее окно с информацией об изменениях в пользовательском соглашении;
  • всплывающее окно, призывающее подписаться (и ещё аналогичный попап в самом браузере);
  • целые блоки жёлтой рекламы через каждые два абзаца текста.

Современные сайты кишат грязными приёмами, суя пользователю прямо в лицо бесполезный и навязчивый контент. Более того, всё это оформлено настолько отвратительно, что порой трудно найти нужный раздел. Заходя на такие сайты, пальцы инстинктивно нажимают Ctrl+W тут же хочется с них сбежать и никогда больше не возвращаться.

Ещё одним ярким примером для меня стал дизайн Microsoft Office, который с объёмного (в версии 2010) сменился на плоский (в версии 2013). Многие элементы стали выглядеть одинаково (например, поля ввода и кнопки), что сильно затрудняло их визуальное восприятие, так как теперь мозгу надо было трудиться больше, чтобы понять, где какой элемент. В случае же объёмного дизайна элементы были легкоразличимы, что позволяло сконцентрировать внимание на работе, а не поиске нужного элемента пользовательского интерфейса среди десятков таких же элементов.

Я очень не хотел, чтобы разрабатываемый нами проект выглядел, как подавляющее большинство современных сайтов и вызывал отвращение как при первом заходе, так и во время его использования. Вместо абстрактных плоских и ничем не разграниченных между собой блоков я использовал образ реального рабочего стола человека, занимающегося рукоделием. Чтобы дизайн сайта был похож на кусочек реального мира, я много времени проводил в поиске подходящих изображений.

Также многое было сфотографировано или отсканировано лично нами и затем обработано в Photoshop.


Кумихимо и бусины

Вот некоторые примеры объектов реального мира, которые были использованы в дизайне:

  • в качестве хедера выступила завалявшаяся у меня подарочная ленточка;
  • меню — это вырванный из моей тетради лист (который изначально был в клеточку);
  • хедер перед контентом страницы — это элемент чехла для читалки электронных книг;
  • всплывающее окошко и нашивка в футере — элемент джинсового рюкзака;
  • кнопка закрытия всплывающего окошка — пуговица с шорт;
  • логотип на главной странице — рисунок от руки;
  • все фенечки и многие бусины, которые можно увидеть перед футером и на странице справа, также пришли из реального мира (некоторые из фенечек были сплетены во время разработки проекта).


Главная страница


Футер

Ещё скриншоты

Страница профиля пользователя


Страница со списком уроков


Всплывающее окошко с выбором языка

Одной из самых трудоёмких работ была подготовка картинок для использования их совместно с CSS-атрибутом border-image. Вся сложность заключалась в том, что определённые части картинки при повторении должны выглядеть бесшовными. Это как рисовать повторяющиеся (бесшовные) текстуры, только сложнее. Больше всего времени ушло на картинку, использующуюся в дизайне всплывающих окон (и нашивке в футере), так как размер элементов, на которые «натягивалась» эта картинка, мог свободно меняться как по вертикали, так и по горизонтали.

Наверное, с дизайном мы заморочились больше, чем с чем-либо другим. При чём, заморочились настолько, что многие элементы окружения (например, бисер и бусины) и некоторые иконки были смоделированы и отрендерены в Blender (что-то даже текстурилось в Substance Painter). Да, ради иконок размером порядка 32х32 пикселя мы заморочились настолько сильно.


Результаты рендеринга

Как создавались элементы дизайна

Моделирование бисера (использован в качестве элемента окружения)


Моделирование бусин (использованы в качестве элемента окружения)


Моделирование пуговицы (использована в качестве элемента окружения)


Моделирование ключа (использован в качестве иконки «Администратор»)


Моделирование карандаша (использован в качестве иконки «Редактировать урок» на кнопке)


Текстурирование бусин в Substance Painter

Мне нравилась идея автоматически генерируемых аватарок пользователей, которые я видел на различных сайта. Но их проблема была в том, что зачастую это были довольно примитивные и однообразные паттерны. Человечки в JIRA выглядели поинтереснее, но всё равно довольно просто и плоско, что не вписывалось в наш дизайн. Ничего путного мне в голову не приходило до того момента, пока мой соавтор не показал мне страничку из старой книги о птицах Великобритании. И тогда я понял, что у меня перед глазами отличная коллекция готовых аваторок. Я попросил отсканировать все страницы с птицами, после чего дня два сидел в фотошопе и нарезал этих птиц на аватарки, по ходу убирая артефакты от книжной бумаги.


После регистрации пользователь случайным образом получал одну из 267 аваторок-птиц

После того, как работа над новым (скевоморфным) дизайном была завершена, и мы завершили его интергацию в подготовленную моим соавтором вёрстку на Bootstrap, я принялся интегрировать новый дизайн. Я думал, что это будет самое неприятное, что я делаю в рамках этого проекта. На самом деле это было просто рутинной девелоперской работой, а самый ад ещё ждал меня впереди.

Развёртывание


Итак, все запланированные фичи были разработаны, все критические и мажорные проблемы — устранены, новый дизайн — натянут, а сайт переведён соавтором на английский язык, пришло время протестировать проект на внешнем сервере. Мы решили сразу развернуть проект, но закрыть к нему доступ паролем, чтобы каким-то образом случайный гость не смог увидеть оставшиеся баги узнать о проекте заранее. Но для старта нам не хватало одной мелочи — названия сайта, которое мы попросту ещё не придумали.

Во время разработки я использовал название «fenker» («Фенькер»), которое по сути было его кодовым именем. Я предложил использовать его в качестве названия сайта, но впоследствии этот вариант был отброшен, так как звучал слишком по-русски. Мы долго перебирали названия: Fenkexchange, Friendship Pigeon, Flossy Hearts, Silky Hearts,…. Да имена всё получались то слишком длинные, то слишком русские. Пока в голову не пришла идея соединить тематику сайта (браслеты дружбы) с одной из его особенностей (птицы: голубь на логотипе и птицы-аватарки). Так и получилась птица-друг — FriendBird.

Пока соавтор занимался регистрацией доменного имени и поиском приемлимого VDS, я устанавливал Debian Linux на VirtualBox, чтобы потренироваться в развёртывании нашего проекта. Раньше я работал с Linux и один период (около года) пользовался только этой операционной системой, но впоследствии забросил, так как «не срослось», в результате чего я вернулся в Windows (в основном из-за недостатка мощного ПО для работы с 2D и 3D графикой); после этого у меня был ещё опыт настройки веб-сервера Apache под Linux в рамках университетских занятий. В целом, при работе с Linux у меня не возникало каких-то серьёзным проблем, поэтому я ожидал, что развёртывание свежеиспечённого проекта пройдёт быстро и ненапряжно. Но в итоге эта активность стала самым неприятным, чем я занимался в рамках этого проекта.

Самый неприятный в моей жизни опыт работы с Linux
Такой уж я человек, который любит, когда установливаешь программу — и она работает. И ладно, если для конфигурации приложения надо почитать документацию, поправить конфиг, набрав несколько команд. Но когда приходится перебирать несколько мануалов, каждый их которых описывает процесс настройки по-своему, а в результате всё равно ничего не работает, то это уже начинает, мягко говоря, напрягать. Я — разработчик, а не сисадмин, и у меня не было когда-либо интереса к настройке веб-серверов под Linux. Мне не доставляет удовольствия красноглазить часами или даже днями ради того, что под Windows в большинстве случаев легко устанавливается, конфигурируется и работает. В Линуксе же настройка каждого приложения мне давалась так, словно я проходил очередной круг ада.

Проблемы начались уже с этапа настройки базы данных: моё приложение наотказ не хотело работать с установленной по умолчанию MariaDB. Обновление коннектора и перебор URL'ов для подключения к БД со всевозможными параметрами не помогал. Удалить MariaDB со всеми зависимостями так, чтобы на её место поставился MySQL от Oracle, мне не удалось. Поэтому пришлось познакомиться с докером, в который и была заселена версия MySQL от Oracle.

Объёмность мануала по настройке почтового сервера заставила меня нервно сглотнуть. Набрав десятки команд и отредактировав несколько конфигов, я в итоге не получил работоспособного почтового сервера. К этому моменту у меня уже не было какого-либо желания выполнять примерно то же самое, только по другому мануалу, поэтому настройку почты я просто забросил. К счастью, впоследствии соавтор проекта нашёл способ настройки почтового сервера через панель управления, предоставляемую хостинг-провайдером.

Но самой хардкорной частью была установка SSL-сертификата. Делал это я впервые, поэтому сначала пришлось много почитать, чтобы хотя бы примерно понимать, как это работает. Получение самого сертификата (мы использовали сертификат от Let's Encrypt) выполнялось в пару кликов в вышеупомянутой панели. Но у меня не было понятия, как правильно его импортировать так, чтобы он был заиспользован Tomcat'ом. С этим вопросом я провозился много дней, перебирая разнообразные способы импорта, следуя разным мануалам, а потом и комбинируя разные подходы. Результата всё не было, и я уже было чуть не опустил руки, как решение всё-таки нашлось (надо было сконвертировать полученный fullchain-сертификат (также используя полученный private key) в формат pkcs12, а затем импортировать получившийся сертификат в JKS (Java Key Store), указанный в конфиге Tomcat'а). После того, как я увидел в браузере заветный зелёный замочек, я почувствовал такое облегчение, словно я сдал самый важный в моей жизни экзамен.

Конечно, опытные сисадмины могут посмеяться с моих «проблем» — и я их прекрасно пойму, так как это их сфера деятельности и область их интересов. Очень жаль, что на этом проекте, который я так хотел выпустить «в люди», мне пришлось заниматься тем, чем заниматься мне совсем не нравится.

Вернувшись из преисподни Завершив свой нелёгкий квест по развёртыванию веб-приложения под Linux и проделав то же самое, но уже на VDS, мы приступили к поиску тестировщиков-волонтёров среди своих друзей, коллег и знакомых. Согласилась лишь пара человек, которые впоследствии не приняли активного участия в тестировании, поэтому тестировать свой проект пришлось нам самим. Около двух недель мы тестировали функционал сайта, находили ошибки, которыми я пополнял свой баг-лист и впоследствии их исправлял. Со временем в баг-листе остались лишь незначительные проблемы, тривиальное решение которых в голову мне не приходило. Фаза тестирования и исправления ошибок близилась к концу, приближая тот торжественный момент, к которому мы оба так долго шли.

Продакшен


Ночью 25 января 2018 года я стёр всё, что было сгенерировано нами в базе данных за время тестирования, и снова запустил процесс инициализации. Когда процесс завершился, я снял пароль на сайт и перезапустил веб-вервер. Теперь сайт был открыт для публики.

Так как соавтор проекта сам когда-то занимался рукоделием, держал собственный сайт на эту тематику и администрировал свою группу Вконтакте, у нас не возникло вопроса, откуда брать изначальную аудиторию. В группу была опубликована новость — и мы увидели первых зарегистрировавшихся пользователей. В первые недели количество просмотров страниц было более тысячи в сутки; немного, но даже этому мы радовались. Но больше всего мы радовались, когда на сайте стали появляться первые результаты обменов фенечками, которых со временем становилось всё больше и больше.

А уроки пользователи не писали. Поэтому, чтобы как-то их расшевелить, соавтор первым опубликовал урок, после чего другие пользователи стали публиковать свои. Больше всего в этом плане выделились ребята из Украины, которые охотно писали уроки и переводили их на украинский язык, за что им отдельное спасибо.

Первый месяц работы сайта выглядел так:

  • 131 зарегистрированный пользователь;
  • 14 полученных фенечек;
  • 15 написанных уроков (включая переводы уроков на другие языки).

Конечно же, во время продакшена находились проблемы, которые я старался оперативно решать, то и дело обновляя сайт. Но со временем приложение стало стабильным, после чего я стал заниматься лишь резервным копированием данных. Всего за время работы сайта (с 2018.01.25 по 2018.07.03 и включая фазу тестирования) было проведено 29 обновлений. Была пара критических обновлений, когда я ночью устранял ошибки, а утром применял обновление.

Не всё сразу нравилось пользователям, а чего-то и вовсе не хватало. Уже во время продакшена была добавлена страница со списком пользователей, страница с формой обратной связи и возможность обмена фенечками только внутри своей страны. После выхода сайта на публику соавтор проекта занимался его продвижением в социальных сетях и сбором отзывов и пожеланий. Мы старались прислушиваться к собравшемуся на сайте сообществу, благодаря которому были исправлены некоторые ошибки и внесены изменения в функционал сайта. Большое им за это спасибо.

Наверное, самой большой проблемой для пользователей сайта стала загрузка фотографий. Для избежания загрузки картинок слишком большого размера, избыточного качества и неподходящего под нашу разметку формата, мы выставили соответствующие ограничения (минимально и максимально допустимые размер файла, высота, ширина и соотношение сторон). Но, судя по логам, пользователям часто очень трудно соблюдать эти ограничения несмотря даже на то, что при каждой попытке загрузки на экран выводится сообщение с указанием проблемы. Я ожидал, что это станет проблемой, но не ожидал, что проблема будет настолько масштабной (настолько, что некоторые пользователи в итоге загружали искажённые или некачественные фотографии, ссылаясь на то, что система не даёт им загружать фотографии в нормальном качестве). Возможно, в будущем я не поленюсь и добавлю-таки обрезатор картинок прямо на сайт или же сделаю автоматическую подгонку изображения под нужный размер.

Настоящее


На момент написания этих строк сайт живёт уже на протяжении 5 месяцев и 1.5 недели, а результаты его работы выглядят следующим образом:

Статистика на 2018.07.05

Для такого периода это, конечно, очень скромные числа; приток пользователей к этому моменту сильно ослаб, а активность по написанию уроков закончилась так же резко, как она и началась. Однако, нас радует то, что обмен фенечками идёт активно, и нерусскоговорящие иностранцы принимают в этом участие.


Статистика по количеству пользователей

Я хорошо понимаю, что сайт можно было бы сделать более популярным за счёт рекламы, но, как я уже говорил, одной из наших задач было придерживаться минимальных финансовых затрат. Запуская этот проект, мы не надеялись на хоть сколь-нибудь значимую прибыль (в силу его некоммерческого характера и специфичности целевой аудитории); в данный момент прибыль с проекта (реклама и пожертвования) не покрывает даже расходы на его содержание (доменное имя, VDS и немного нашего времени на поддержку). Но, как сказал один мой австралийский знакомый, если проект сделал счастливыми хотя бы пару человек, усилия на его разработку были потрачены не зря.

Даже несмотря на совсем небольшой интерес со стороны публики и некоторые несовершенства самого сайта (например, отсутствие CDN и двухфакторной аутентификации), мне нравится то, каким получился наш проект. На протяжении около года я проделывал большую работу, получая огромное удовольствие от решения самых разных задач и стараясь делать всё, что было в рамках моих интеллектуальных возможностей. Также в процессе разработки меня радовал тот факт, что надо мной нет каких-либо заказчиков, меня не подгоняют сроки сдачи проекта, а стиль написания кода не сковывается навязанными извне соглашениями; полная свобода принимаемых мною решений придавала особый вкус удовольствию от работы над этим проектом.

Комментарии (7)


  1. demimurych
    11.07.2018 19:54
    +3

    Простите, но зайдя на Ваш сайт и попробовав им попользоваться, я бы предпочел — большие картинки увеличенные футеры и кричащие цвета, уже хотя бы потому, что я ими могу пользоваться, а тем что у Вас сейчас — только из под палки.

    Трудно читаемые шрифты, шумящий фон вызывающий раздражение и т.д. и т.п. Это уж совсем не альтернатива современному дизайну. Поймите, современный дизайн возник не просто так, потому что модно.


    1. Inexu Автор
      11.07.2018 23:29

      Да, я отлично понимаю, что трендовые дизайны взяты не с потолка (это дешевле, легче дизайнить под мобильные устройства, меньше затраты (и трафик) на графику, меньше визуальных багов и т.д.). И я никак не позиционировал этот дизайн как альтернативный трендовому.

      Мы создавали дизайн, который был бы приятен нашему глазу, и, как мы надеялись, целевой аудитории. Да, прорабатывать такой дизайн куда сложнее, но у нас была возможность погрузиться с головой и создать нечто чуть более аутентичное и чуть менее обыденное. Создать то, что нам действительно нравится.

      >> Трудно читаемые шрифты
      Хм, это интересно. В качестве шрифтов у нас в CSS указаны "«Helvetica Neue», Helvetica, Arial, sans-serif". А какие шрифты наиболее легкочитаемы лично для вас?

      Да, выбранный нами дизайн придётся по вкусу далеко не всем, ведь у каждого свои предпочтения и привычки. Поэтому и мнения разные. И спасибо, что поделились своим!


  1. Samouvazhektra
    11.07.2018 20:48

    Напомнило опыт разработки своих первых проектов :-) И дизайн вызвал умиление, не без косяков, у меня контент уезжает влево от "кожаной" плашки, и с цветовой гаммой явно перебор для восприятия, думаю стоит немного упростить, но от концепции отказываться не стоит — для ЦА самое то. Современный дизайн — это модно, быстро, эффективно, адаптивно, универсально, удобно для бизнеса и разработчика… и безлико.


    1. Inexu Автор
      11.07.2018 23:04

      Да, точно: есть баг с уезжанием контента. Он воспроизводится, когда блокировщик рекламы вырезает левый рекламный блок вместе со счётчиком посещений. В ближайшие дни постараюсь исправить.


  1. noodles
    11.07.2018 21:57
    +1

    Над типографикой и визуальными слоями можно ещё поработать. А так… обалденный дизайн и его история. Мне очень нравится.


  1. kinall
    12.07.2018 12:06

    Спасибо за рассказ! Очень хорошо написан, приятно было прочитать. И я вам очень завидую – вы нашли интересное для себя дело и посвятили ему столько времени, сколько было нужно. Рад за вас!


  1. Yasunako
    12.07.2018 12:07

    Видно, что человек старательно и с душой подошел к проекту. Когда я делал подобный проект (с бОльшим кол-вом фич и функционала) на заказ, заказчик сам не до конца понимал, что ему нужно, да и с ТЗ были проблемы. В итоге получилось что-то громоздкое, без уникального дизайна, с непонятным функционалом и кривым обменом. После чего заказчик пропал, сайтом бросил заниматься… даже рыбу-текст не заменил в футере… Два года проект «плывет», статистика печальная…
    Вам же желаю удачи и развития проекта.