Ключевой урок: построение CloudRepo с помощью Clojure

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

Выиграйте или проиграйте, результат зависит от вас, так что лучше не ошибаться, верно?

Безопасные стеки технологий для использования - это те, которые используют «все остальные».

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

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

Это не то место, где я буду, и не то место, где я планирую вести свою компанию.

Сегодня стадо использует Java.

Мы создали CloudRepo с помощью Clojure.

Мы узнали кое-что, и мы хотели бы ими поделиться.

Радость Clojure

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

Почему мы выбрали именно его?

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

Единственный способ определить, что для нас «лучше» - это что-то построить с его помощью, в данном случае это CloudRepo.

Кстати, мы огромные поклонники Рича Хикки - в одиночку он оказал наибольшее влияние на мой рост как инженера-программиста. Одни его выступления сделали меня лучшим инженером не только на Clojure, но и на всех других языках, которые я использовал с тех пор.

Сам Clojure оказал второе влияние на мой рост как инженера. Это избавило меня от объектно-ориентированного мышления и действительно расширило мои знания о языках программирования в целом.

Если часть успеха - это обучение (а для нас это так), то чему мы можем научиться, используя Clojure на постоянной основе?

Мы решили выяснить.

Смена парадигмы

Сначала Clojure может показаться неудобным или вам может показаться, что вы боретесь с ним, чтобы сделать что-то простое, особенно если вы имеете солидный объектно-ориентированный (OO) фон с использованием таких языков программирования, как C ++, C # или Java.

Это происходит потому, что для перехода от объектно-ориентированного проектирования к Clojure требуется полная смена парадигмы.

«Данные - это API» - это парадигма, которая правит в Clojure.

Хм? Что это обозначает?

Это термин, который я услышал от члена сообщества Clojure и обнаружил, что это ключ к использованию возможностей Clojure.

Я постараюсь кратко объяснить сейчас и приведу пример позже в этом посте:

В Java определения наших классов определяют API - мы тратим много времени на выяснение того, как наши классы интегрируются друг с другом. Даже данные инкапсулируются в классы, у которых есть свои собственные API.

Мы потратили бесчисленные часы ошеломляющей работы в нашей карьере, связывая классы друг с другом, написав код типа a.setFoo(b.getFoo);, когда у нас были данные аналогичной формы, но разных статических типов.

В Clojure, если вы принимаете парадигму «Данные - это API», то проектирование систем в первую очередь связано с идентификацией ваших данных и того, как они проходят через ваши функции. Данные могут быть представлены небольшим набором абстракций, в частности картой, что снизит потребность в написании связующего кода, который так часто встречается в Java (a.setFoo(b.getFoo) и т. Д.).

Наш опыт

Когда мы создавали CloudRepo, мы узнали, что все еще думаем как объектно-ориентированные программисты. Clojure боролся с нами по пути, и в конце концов мы получили сообщение. Когда мы начали применять смену парадигмы, работать с Clojure стало легче.

Сегодня работа с Clojure доставляет удовольствие - мы научились думать о системе как о данных, проходящих через нее, и о том, как эти данные преобразуются и обрабатываются.

Я считаю, что именно это имеется в виду под «Данные - это API».

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

Мы начали использовать Clojure именно таким образом, и тогда мы почувствовали, что он борется с нами.

Потому что он боролся с нами.

Поскольку вы не должны программировать на Clojure, как на объектно-ориентированном языке.

Если вы возьмете одну вещь из этого поста, я надеюсь, что это так.

Идиоматический Clojure

Идиоматическое программирование в Clojure похоже на создание конвейера функций:

Вот где вы действительно можете увидеть разницу между статической типизацией и динамической типизацией.

В OO, когда все ваши функции принимают другой тип (например, объекты данных или классы типов), вам нужно написать много связующего кода для управления отображениями / преобразованиями между полями.

В Java вы можете использовать лямбды для небольшого упрощения, но ваши функции сопоставления все равно должны знать, как тип A сопоставляется с типом B.

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

Если вы передадите карты в Clojure и примете эту концепцию, Clojure перестанет сражаться с вами и раскроет вам объятия, как лучший друг.

Это был очень важный урок, на который у нас ушло больше времени, чем следовало бы выучить. Все эти годы объектно-ориентированного программирования потребовалось довольно много времени, чтобы преодолеть его.

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

Ключевые концепции - данные как API

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

Имея почти 20-летний опыт объектно-ориентированного проектирования, было трудно изменить наш подход к проблемам программного обеспечения.

Передача карт в качестве параметров

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

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

Этот пример показывает, сколько шаблонов требуется в Java только для правильного подключения. В программе очень мало логики, т.е. «Много церемоний».

В Clojure идея состоит в том, что большинство функций работают с небольшим набором абстракций, одна из которых является абстракцией карты (ассоциативной). Если вы пишете функции, которые принимают и возвращают абстракцию карты, вы можете объявить предыдущий код Java в Clojure следующим образом:

Это гораздо более лаконично и просто, и мы все стремимся к тому, чтобы наши программы были такими.

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

Проектирование на основе потоков данных

Когда вы разрабатываете программы на основе объектно-ориентированного программирования, вы получаете множество диаграмм последовательностей, показывающих взаимодействия между объектами (например, диаграммы последовательности Google UML).

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

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

В Clojure функции вызывают функции - единственное различие между функциями - это арность - однако, если вы сойдетесь при передаче карт, арность ваших функций станет 1, просто карта. Связь была перемещена от сигнатур типа (объект к объекту) к форме данных (т. Е. Передаче карт с ожидаемыми ключами :a.)

В Java даже классы данных предоставляют свой собственный набор API - не существует общей абстракции, которую можно было бы легко использовать для представления данных так же, как это делает Clojure. Вы должны написать собственный код для работы с каждым отдельным типом данных.

В Java есть карты, и вы можете передавать их, как в Clojure, но ключи вводятся, и даже если вы попытаетесь использовать строки в качестве ключей, вам придется много работать, потому что классы данных, абстрагируемые API-интерфейсами идиоматичны для Java, Карты - нет.

Clojure функционален, и поскольку «данные - это API», наше моделирование сильно отличается. Вместо моделирования взаимодействия между объектами в системе мы моделируем данные и то, как данные проходят через систему и развиваются при прохождении через каждую функцию.

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

Данные как API - пример дизайна

В следующем примере показан поток данных в CloudRepo при поступлении запроса на получение артефакта.

Когда в CloudRepo поступает запрос на получение артефакта, мы получаем два бита данных :token и :uri в нашей входной карте и трансформируем эту карту, пока не дойдем до окончательного результата и не вернем ответ клиенту.

Прямоугольники - это функции, а параллелограммы - данные, которые передаются между ними. Обратите внимание на то, как данные «перетекают» на диаграмму.

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

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

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

Как только мы начали разрабатывать и реализовывать наш код таким образом, в наших головах загорелась лампочка. Этот материал может быть невероятно мощным и может сэкономить нам огромное количество времени.

Примите силу

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

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

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

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

Когда этот день наступит, вы будете на световые годы впереди них.