Clojure для портала по продаже недвижимости

Оригинал

Около года назад ко мне обратился бывший работодатель с просьбой помочь в разработке очень большого проекта на Java. Это был новый портал о недвижимости, в котором были предусмотрены отдельный раздел для агентов, надежная и масштабируемая обработка данных, внутренняя система администрирования для группы поддержки и масса данных для импорта, а также инструменты для обработки данных. Проект имел жесткий срок сдачи - около 10 месяцев до релиза и обещал “громко” запуститься в Великобритании. Сайту нужно было быть быстрым, надежным и красивым.

Сначала я чуть было не отказался в связи с моим персональным нежеланием участвовать в разработке большой статически типизированной системы. Я не буду погружаться в детали тысячелетних холиваров очередной раз: статическая или динамическая типизация, ООП или функциональная парадигма. Достаточно сказать, что, имея опыт работы и с Java, и с Clojure проектами совместно с очень сообразительными людьми, я убедился, что Clojure предлагает более простой подход к программированию, в основном, за счет решенных “из коробки” вопросов об иммутабельности и персистентности данных. Выбор Java в данном случае (ввиду жесткости сроков) был бы рискованным. Разработчики склонны тратить много времени, “полируя” объектную модель, что может впоследствии оказаться неоправданным. В своем ответе я предложил использовать Clojure, не особо надеясь, что заказчик согласится.

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

Архитектура должна была быть гибкой. Задача всегда сложнее, чем мы думаем о ней сначала, так что нам нужна была возможность ошибаться и исправлять ошибки. Мы строили систему снизу вверх, избегая неоправданных высокоуровневых абстракций. Также мы не стали делать микросервисы, раз и навсегда решив не разделять кодовую базу. Код на Clojure сам по себе хорошо разделяется из-за отсутствия объектов и статических типов, поэтому его не надо дробить преждевременно. Такой подход консервативен, зато вам не придется все переделывать, если при разделении на микросервисы что-то пойдет не так.

Для веб-приложений мы использовали Ring, Compojure, Bidi и http-kit, добавляя Friend и Liberator там, где было уместно. Ходят споры, так ли Liberator хорош для веб-приложений в целом, как для REST-сервисов, но все остальные используемые нами инструменты прекрасно сочетаются друг с другом.

Для хранения данных, предназначенных для фронтенда мы взяли Elasticsearch. Лично я считаю ES хорошо зарекомендовавшим себя решением и уже использовал его в двух проектах ранее. ES очень быстрый за счет большого кеша в оперативной памяти, а его поисковое API превосходно. С новыми возможностями, такими как, например, “aggregations” можно решать самые различные проблемы. ES основывается на JSON и прозрачно работает с Clojure стэком, где десериализация JSON через REST - тривиальная задача.

На бекенде мы воспользовались проверенной и испытанной Postgres. Вы оцените этот “проторенный путь”, и если вам среди ночи взбредет в голову решить какую-то невероятную проблему, скорее всего, до вас ее кто-то уже решал, и тогда вы похвалите себя, что сделали такой выбор. Мой коллега Мартин Трожер (Martin Trojer) проделал отличную работу, сделав библиотеку для миграции базы данных и других хранилищ данных, а также для получения и загрузки тестовых данных в локальную базу данных перед тестовым прогоном.

Но не все было так жизнерадостно. Мы пробовали Storm для управления асинхронной обработкой данных – он хорош, но предназначен скорее для числовых данных, чем для того, чтобы “гонять” большие документы по узлам. Когда вам нужно постоянно опрашивать несколько хранилищ и обновить какое-то центральное состояние, вы не вписываетесь в схему работы Storm, для которого идеально иметь коллекцию обрабатывающих юнитов, делающих одну атомарную работу. Бесспорно, Storm тяжело устанавливать в облаке и к нему тяжело обращаться через REPL. Для таких задач недавно появился Onyx, однако эта система уже заслуживает внимания.

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

Однажды JUXT проводил проверку нашей работы в рамках стандартной для консультантов процедуры. Мы пригласили независимого человека, чтобы он тщательно оценил инструменты и приемы, которыми мы пользовались. Он сподвиг нас быть гибче и проще, так что в итоге мы выкинули несколько “жемчужин кода”, которые добавляли больше проблем, чем решали. Да, не каждое наше решение моментально приносило положительные плоды, но это не так важно. Важнее, что общекомандное стремление к простоте сделало архитектуру гибкой в духе agile.

Невероятно приятной стороной проекта была сублимация опыта разных сеньор-разработчиков Clojure. Например, до этого момента я не смотрел на перспективы использования библиотеки prismatic-schema, ощущая за этим сходство с моделью разработки в духе Java/.NET. Теперь я думаю, что это прекрасный инструмент, ставший очень полезным для нас, обеспечивая нас “контрактами” при написании обработчиков и трансформациях данных. Мы взяли лучшую часть ООП – декларативность при описании схемы данных – и органично вписали ее в систему. У нас не было нужды внедрять ее с самого начала.

Затем кто-то посоветовал мне Swagger, предлагающий красивый интерфейс “из коробки” на основе написанной prismatic схемы данных, описывающей REST сервис. Мы не хотели отвлекаться на лишнее, и тот факт, что мы не тратили время на такой интерфейс просто потому, что удачно выбрали инструменты, очень нас обрадовал.

Разработка проекта была не только интересной, но бросала нам невиданный мной ранее вызов по срокам сдачи работы (из-за запущенной по телевизору рекламы). В частности, увеличивая команду, нужно было организовать единое для всех понимание задач и целей. Зачастую бывает, что команды, используя новые технологии без устоявшихся best practices, склоняются к демократии в принятии решений, которая приводит к затянутым спорам “об архитектуре вообще”, и в запущенной форме сильно мешает развитию продукта. Порой я чувствовал вину, навязывая авторитарный выбор более опытного разработчика.

Еще одной трудностью была общекомандная ответственность за код. Программирование на Lisp-подобном языке скорее связано с индивидуалистским подходом, подогретом в нашем случае желанием успеть сделать продукт в срок. Единство образа мышления команды временами “проседало”, но это, несомненно, тот аспект, который должен быть учтен после запуска, когда на первый план выходят более стратегические цели.

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

В отличие от многих других “Clojure-проектов”, в этот раз язык не приводил к расколу или восторгу команды, которые обычно сопровождаются некоторым обучением и навящевой рекламой преимуществ Clojure перед другими языками. В данном случае Clojure был выбран с самого начала, у нас был срок сдачи и представление, что к этому сроку нужно сделать. Поэтому у нас не было ощущения рискованной новой игрушки, а еще одного языка, с которым мы работаем как обычно. Хотя такое ощущение иногда появлялось, когда мы натыкались на какую-нибудь “сырую” библиотеку, или когда мы (изредка) устраивали дискуссии об идиоматичности использования некоторых паттернов программирования.

Мы поверили в Clojure, но не только это. Люди ждут, что ты возьмешь и станешь разрабатывать на динамических языках быстрее, чем, например, на Java или .NET, и я думаю, что это так. Но для меня самым большим преимуществом в выборе Clojure было то, что это иммутабельный функциональный язык. Когда традиционные объектно-ориентированные системы начинают гнуться под собственным весом, а бизнес-логика не просматривается за многочисленными рефакторингами и паттернами проектирования, Clojure, напротив, предстает во всем своем великолепии. Бизнес-логика лежит на поверхности, с ней легко работать, и дедлайн видится чуть более достижимым.

Спасибо @s-mage за участие в переводе :)