Программисты пишут свои вики в obsidian, в org файлах и других инструментах. С ними готовятся к собеседованию, ищут команды для баша, хранят информацию про их сервер и много чего еще.

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

Можно сделать персональный монорепозиторий, в котором будет храниться весь код, как вики, но для кода.

Стек

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

Вместо Clojure можно использовать Python и JS, и в целом использовать несколько языков одновременно.

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

Технические детали

Верхнеуровнево Polylith репозитории устроены так:

$ tree -L 1

.
├── bases
├── components
├── development
├── projects
├── build.clj
├── deps.edn
├── Makefile
├── package.json
├── readme.md
└── workspace.edn

У Polylith’a есть CLI-утилита, которая помогает правильно организовать репозиторий и создавать компоненты, однако без нее можно обойтись: Polylith – это прежде всего архитектура монорепозитория, и ее можно создать и поддерживать вручную.

Базово Polylith репозитории состоят из четырех директорий:

  • в components находятся все компоненты, которые проекты могут переиспользовать, например:

    • компонент авторизации,

    • логов,

    • интерфейс работы с базой данных,

    • интерфейс работы с 3rd party сервисами,

    • доменные сущности: если в нескольких проектах используется модель user с устоявшимся интерфейсом, то можно вынести ее из проектов и сделать отдельным компонентом.

  • В bases находятся базовые блоки для проектов. Каждый блок определяет интерфейс какого-то публичного API, имплементирует его с помощью REST, RPC, командной строки и вызывает один или больше компонентов для функционала.

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

  • Каждый проект в projects обычно состоит из одного файла с перечислением всех нужных компонент из components и одного базового блока из bases.

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

Еще в корне репозитория находятся служебные файлы, например, deps.edn описывает все компоненты и базовые блоки для локальной разработки, build.clj и Makefile хранят вспомогательные команды для компиляции проектов и для тестирования.

Так как помимо Clojure я использую ClojureScript – диалект Clojure, который компилируется в Javascript, а не в JVM – в корне проекта находится package.json с dev-зависимостями ClojureScript’а.

В workspace.edn хранятся метаданные для Polylith CLI-команды, которую я практически не использую.

Примеры проектов и миграция репозиториев в монорепозиторий

Мигрировать все проекты в монорепозиторий можно постепенно. Сначала можно создать базовый блок и скопировать весь код туда. Это называется fat base – “толстый” базовый блок.

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

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

$ tree -L 1 components
./components
└── postman

В директории postman лежит аналог curl или postman для работы с интересующими меня сервисами в REPL.

В bases находятся толстые базы пет-проектов, которые существовали еще до монорепозитория.

$ tree -L 1 ./bases
./bases
├── clj_scraper
├── clj_tasks
├── doodle_editor
├── fe_experiments
└── truth_or_dare_clj

Например, в fe_experiments лежит сайт на ClojureScript, там я изучал DOM и как им манипулировать. Если это понадобится еще раз, выделю утилиты для DOM в отдельный компонент или хотя бы grep’ну примеры использования.

В clj_tasks находится сборная солянка из разных задач: Advent of Code разных лет, leetcode (его просмотр помогает при подготовке к собеседованиям), тестирование jupyter notebook’ов с JS рантаймом Deno и даже презентация про мемы про горилл на аналоге jupyter notebook в Clojure - библиотеки clerk:

$ tree -L 1 ./bases/clj_tasks/src/snyssfx/clj_tasks/
./bases/clj_tasks/src/snyssfx/clj_tasks/
├── aoc2024
├── aoc2025
├── clojure_camp
├── common
├── curl_command.go
├── di
├── external_ip.go
├── gm.clj
├── gm.sql
├── gorillas.clj
├── hs.clj
├── leetcode
├── logic
├── lucky_tickets.clj
├── mb
├── our_mathematical_universe
├── other
├── portal.clj
├── test_deno.ipynb
└── vi.clj

В projects находится несколько проектов, например, проект для сайта выглядит вот так:

$ tree -L 1 ./projects/fe_experiments
./projects/fe_experiments
├── deps.edn
├── node_modules
├── package.json
├── package-lock.json
├── shadow-cljs.edn
└── target

Тут описание JS и CLJS зависимостей, node_modules с этими зависимостями, а также забандленный сайт в target, который достаточно скопировать на сервер.

Пример локальной разработки на Clojure

Директория development выглядит так:

$ tree -L 1 ./development/src
./development/src
├── doodle_fiddle.clj
├── fe_experiments_repl.clj
├── fe_experiments_repl.cljs
├── fiddle.clj
├── fp_slides.clj
├── skia_fiddle.clj
└── truth_or_dare_repl.clj

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

Есть 2 сценария как добавлять код в репозиторий: писать новый код или переиспользовать старый.

Эксперименты с новым кодом в Clojure

Допустим, я хочу попробовать новый функциональный GUI для Clojure membrane.

Я открою туториал с примерами использования, и в ./development создам новый файл membrane_fiddle.clj, и открою его:

Затем запущу REPL в редакторе (в Emacs это одна команда cider-jack-in) и проверю, что REPL работает, сложив 2 и 2:

В Clojure принято использовать REPL-driven подход, когда вы вводите текст в редакторе и по горячей клавише выполняете его. Ничего не надо переписывать руками, редактор уже подключен к терминалу. Нужно только навести курсор на конец строки и (в случае Emacs) нажать Ctrl+C Ctrl+E.

Чтобы добавить библиотеку в зависимости, тоже можно использовать REPL:

Затем копирую пример из туториала и выполняю его, и сразу увеличиваю шрифт и указываю размер окна:

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

Помимо шутки вернулось много вспомогательных данных, и разбираться в них довольно сложно. В такие моменты я открываю portal – удобный viewer структур данных для Clojure. Он уже настроен в этом репозитории, так что открывается по одной команде Emacs:

Теперь понятно: нужно взять body, распарсить json и оттуда взять поле “joke”. Сделаем это и выполним код еще раз. Для парсинга json’a я использую одну библиотеку во всем репозитории, так что это довольно просто:

Добавлю это выражение вместо Hello World и выполню весь код еще раз:

К сожалению, хорошую шутку вывести не удалось, однако какую-то шутку удалось вывести. Успех!

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

Так я использую Clojure REPL и вместо командной строки, и визуализируя маленькие команды и тексты, и для написания веб-серверов в пет-проектах.

Использование существующего кода

Помимо общих библиотек можно выделять отдельные компоненты. Например, для application-сервера я везде использую одну и ту же связку из библиотеки для роутинга reitit, библиотеки-интерфейса ring, а также функций для старта и остановки сервера из REPL. Все это можно было бы выделить в отдельный компонент server и импортировать в новые проекты.

Заключение

Мне очень удобно держать все проекты в одном месте и строить новые проекты на наработках из старых. Динамический live язык программирования с REPL’ом помогает. Интересно, как бы это выглядело на более live языках типа SmallTalk.

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

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

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