Организация нескольких взаимосвязанных проектов sbt и git в scala — лучшие практические советы

С scala, используя sbt для сборки и git для контроля версий, как лучше организовать код вашей команды, когда он перерастет единый проект? В какой-то момент вы начинаете думать о том, чтобы разделить свой код на отдельные библиотеки или проекты и импортировать между ними по мере необходимости. Как бы вы организовали вещи для этого? или вы бы избежали искушения и просто управляли бы всеми пакетами в одном и том же «проекте» sbt и git?

Достопримечательности: (не стесняйтесь менять)

  • Избегайте изобретения новых «головных болей», которые переоценивают воображаемые потребности.
  • Вы по-прежнему можете легко создать все, когда захотите, на заданной машине разработки или CI server.
  • Упаковка для производства: возможность использовать SbtNativePackager для упаковки ваших материалов для производства без особых усилий.
  • Легко контролируйте, какую версию каждой библиотеки вы используете на данной машине разработки, и легко переключайтесь между ними.
  • Избегайте того, чтобы манипуляции с git стали хуже, чем обычно.

Кроме того, вы бы использовали какой-то «локальный репозиторий команды sbt/maven» и что для этого нужно сделать? надеюсь, это не обязательно.

Спасибо!


person matanster    schedule 21.10.2014    source источник
comment
В основном это сильно зависит от характера проекта и от того, в какой степени он требует или вам нужна модульность. В любом случае, один из вариантов — сохранить их как отдельные модули, но в многопроектная конфигурация. Таким образом, вы можете агрегировать их в родительском проекте, так что это можно считать хорошей вещью, особенно когда команда находится на ранних стадиях разработки. Таким образом, вы сохраняете возможность легко разделить их позже, но вы можете собрать все и запустить тесты с помощью одной команды sbt.   -  person Nader Ghanbari    schedule 21.10.2014
comment
Это здорово, но я не уверен, что следую определенным там зависимостям пути к классам. Означают ли они, что один проект автоматически получит путь к классам своего classpath dependency, или это также означает, что компиляция одного всегда компилирует другой?   -  person matanster    schedule 05.11.2014
comment
Под зависимостью classpath они подразумевают межмодульные зависимости, которые довольно гибки, в том смысле, что вы можете зависеть test от test и compile от test или compile от compile или даже compile от test, что очень полезно. Итак, вкратце, это означает, что когда проект A зависит от B по .dependsOn(B), по умолчанию вы можете использовать все классы в проекте B в проекте A.   -  person Nader Ghanbari    schedule 05.11.2014
comment
Но агрегация — это другое, это означает, что когда проект A агрегирует проекты B и C (независимо от того, зависит от них или нет), при сборке A, B и C будут собираться автоматически. Это также может быть очень полезно, когда вы хотите протестировать или скомпилировать их все вместе.   -  person Nader Ghanbari    schedule 05.11.2014
comment
Итак, я полагаю, что зависимости пути к классам также заботятся об автоматическом предоставлении выходных данных проектов зависимостей в пути к классам... Я думаю, используя подмодули git или поддеревья git для каждого проекта multi-project, вы получаете хорошую гибкость при управлении версиями всего этого...   -  person matanster    schedule 05.11.2014
comment
Да, это так. И да, это хороший способ управлять подпроектами.   -  person Nader Ghanbari    schedule 06.11.2014


Ответы (2)


Я использую следующие линии на песке:

  • Код, который в конечном итоге находится в разных развертываемых компонентах, находится в разных папках в одном и том же репозитории в рамках зонтичного проекта — то, что SBT называет многопроектная сборка (я использую maven, а не SBT, но концепции очень похожи). Он будет построен/развернут в разных банках.

Я стараюсь учитывать окончательные возможности развертывания при создании дивизий, которые имеют смысл. Например, если в моей системе foosys есть развертываемые компоненты foosys-frontend и foosys-backend, где foosys-frontend создает HTML-шаблоны, а foosys-backend общается с базой данных, а оба обмениваются данными через REST API, то я буду использовать их как отдельные проекты и проект foosys-core для общего кода. . foosys-core не может зависеть ни от библиотеки шаблонов html (потому что foosys-backend этого не хочет), ни от библиотеки ORM (потому что foosys-frontend этого не хочет). Но я не беспокоюсь об отделении кода, работающего с библиотекой REST, от «основных объектов предметной области», потому что и foosys-frontend, и foosys-backend используют код REST.

Теперь предположим, что я добавляю новый развертываемый объект foosys-reports, который обращается к базе данных для создания некоторых отчетов. Затем я, вероятно, создам проект foosys-database, зависящий от foosys-core, для хранения общего кода, используемого как foosys-backend, так и foosys-reports. И поскольку foosys-reports не использует библиотеку REST, мне, вероятно, следует также отделить foosys-rest от foosys-core. Таким образом, я получаю библиотеку foosys-core, еще два проекта библиотек, зависящих от нее (foosys-database и foosys-rest), и три развертываемых проекта (foosys-reports в зависимости от foosys-database, foosys-frontend в зависимости от foosys-rest и foosys-backend в зависимости от обоих).

Вы заметите, что это означает наличие одного проекта кода для каждой комбинации развертываемых компонентов, в которых может использоваться этот код. Код, который входит во все три развертываемых компонента, находится в foosys-core. Код, который входит только в один развертываемый компонент, входит в проект этого развертываемого компонента. Код, который находится в двух из трех развертываемых компонентов, находится в foosys-rest или foosys-database. Если бы мы хотели иметь некоторый код, который был частью развертываемых модулей foosys-frontend и foosys-reports, но не развертываемых компонентов foosys-backend, нам пришлось бы создать другой проект для этого кода. Теоретически это означает экспоненциальный рост количества проектов по мере добавления дополнительных развертываемых компонентов. На практике я обнаружил, что это не слишком проблематично — большинство теоретически возможных комбинаций на самом деле не имеют смысла, поэтому пока мы создаем новые проекты только тогда, когда у нас действительно есть код для их добавления, все в порядке. И если мы закончим с парой классов в foosys-core, которые на самом деле не используются в каждом отдельном развертываемом, это не конец света.

С этой точки зрения тесты лучше всего понимать как еще один вид развертываемых средств. Поэтому у меня был бы отдельный проект foosys-test, содержащий общий код, который использовался для тестов для всех трех развертываемых проектов (в зависимости от foosys-core), и, возможно, проект foosys-database-test (в зависимости от foosys-test и foosys-database) для вспомогательного кода теста (например, код настройки теста интеграции базы данных). ), что было общим между foosys-backend и foosys-reports. В конечном итоге мы можем получить полную параллельную иерархию из -test проектов.

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

Код в разных репозиториях обязательно версионируется независимо, поэтому в некотором смысле это бессодержательное определение. Но я думаю, что вам следует переходить на отдельные репозитории git только тогда, когда это необходимо (аналогия с этот пост): вы должны использовать Hadoop только тогда, когда ваши данные слишком велики, чтобы использовать что-то более удобное). Как только ваш код находится в нескольких репозиториях git, вам нужно вручную обновить зависимости между ними (на машине разработки вы можете использовать зависимости -SNAPSHOT и поддержку IDE, чтобы работать так, как если бы версии все еще были синхронизированы, но вы должны вручную обновить это каждый раз, когда вы выполняете повторную синхронизацию с мастером, что добавляет трения в разработку). Поскольку вы делаете релизы и обновляете зависимость асинхронно, вам нужно принять и внедрить что-то вроде семантического управления версиями, чтобы люди знали, когда безопасно обновлять зависимость от foocorp-utils, а когда нет. Вы должны публиковать журналы изменений, иметь сборку CI с ранним предупреждением и более тщательный процесс проверки кода. Все это потому, что цикл обратной связи намного длиннее; если вы сломаете что-то в нижестоящем проекте, вы не узнаете об этом, пока они не обновят свою зависимость от foocorp-utils, спустя месяцы или даже годы (да, годы - я был свидетелем этого, и в стартапе из 80 человек, а не мегакорпорации ). Поэтому вам нужен процесс, чтобы предотвратить это, и все становится соответственно менее гибким.

К уважительным причинам для этого относятся:

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

Я бы использовал репозиторий команды maven, возможно, Nexus. На самом деле я бы рекомендовал это еще до того, как вы перейдете к стадии нескольких проектов. Его очень легко запустить (просто приложение Java), и вы можете проксировать свои внешние зависимости через него, что означает, что у вас есть надежный источник для ваших зависимостей jar, и ваши сборки будут воспроизводимы, даже если одна из ваших исходных зависимостей исчезнет.

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

person lmm    schedule 21.10.2014
comment
Спасибо @Imm, хотя мой сценарий может иметь другое сочетание нюансов, это обсуждение очень полезно! также ссылка на сообщение в блоге, если оно будет написано, было бы неплохо здесь в будущем. Nexus выглядит действительно круто — приятно знать. Мне нравится рекламируемая функция проксирования, которая, кажется, устраняет хрупкую временную зависимость от внешних ресурсов. Однако мне интересно, в какой момент бесплатной версии больше не достаточно, и вам нужно сделать скачок .... - person matanster; 05.11.2014
comment
Должен ли я считать, что при использовании Nexus вы не используете ни подмодули git, ни git subtree, так как каждый проект просто получает свои зависимости от Nexus? разве это не идет наперекосяк в тех случаях, когда вы хотите изменить несколько репозиториев более или менее одновременно, скажем, при экспериментальной разработке... Я имею в виду, что бедному разработчику тогда нужно будет заново сшить всю интеграцию между репозиториями, комментируя из зависимостей Nexus - что может занять очень много времени при переключении контекста, а также небольшой кошмар :(... - person matanster; 05.11.2014
comment
@Imm, вы упоминаете много архитектуры вокруг развертываемых объектов, о каких размерах развертываемых объектов мы говорим в этом сценарии? (мои файлы jar не такие большие, по крайней мере, до их упаковки вместе со всеми их зависимостями для производства, и у меня есть микросервисная архитектура). - person matanster; 05.11.2014
comment
У меня никогда не было проблем с использованием бесплатной версии Nexus с репозиториями порядка терабайта; Я думаю, что платная версия добавляет дополнительные функции, а не вопрос размера или чего-то подобного. - person lmm; 06.11.2014
comment
Да, я избегаю подмодулей или поддеревьев git. Поскольку я стараюсь, насколько это возможно, хранить все вместе в одном репозитории git, разделяя его только тогда, когда проект логически отделен или над ним работает другая команда, редко требуется вносить изменения в несколько разных репозиториев git (в модулях maven). которые версионируются и выпускаются вместе, это нормально) - обычно одна команда вносит и тестирует свои собственные изменения и проходит через цикл выпуска, и только после этого другая команда обновляет свою зависимость. И помните, что не каждый выпуск maven должен соответствовать полному развертыванию. - person lmm; 06.11.2014
comment
Обратите внимание, что для модулей в одном и том же репозитории git все они наследуют (включая их версии) от общего родителя и используют ${project.version}, когда зависят от другого проекта в том же репозитории, поэтому во время разработки все проекты зависят от версии разработки друг друга и изменения будут отражены мгновенно (в eclipse) или в следующий раз, когда вы создадите весь репозиторий (в командной строке). Выпуски происходят одновременно с использованием подключаемого модуля выпуска maven, поэтому для любого выпуска существует единый тег и общая версия. - person lmm; 06.11.2014
comment
Тем не менее, когда вам нужно разрабатывать проекты из разных репозиториев, связать их довольно просто — просто измените версию зависимости на соответствующую -SNAPSHOT, а в eclipse проекты просто зависят друг от друга и изменяются в одном мгновенно отражаются в другом. В командной строке вы должны построить зависимость перед зависимым проектом, что довольно хлопотно, но на самом деле достаточно справедливо. Плагин выпуска maven не позволяет вам делать выпуск с зависимостью -SNAPSHOT, поэтому он требует, чтобы вы сначала выпустили зависимость. - person lmm; 06.11.2014
comment
Я не думаю, что размер развертываемых объектов имеет большое значение. Я использовал такую ​​структуру в монолитном проекте ~500kloc с примерно 6 развертываемыми компонентами, и я использовал ее в микросервисном проекте ~20kloc с десятками развертываемых объектов. Некоторых людей беспокоит наличие большого количества модулей maven для относительно небольшого объема фактического кода, но я еще не видел, чтобы это вызывало какие-либо практические проблемы. - person lmm; 06.11.2014

Я немного опоздал, но мои 2 цента.

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

Общие проекты

  • App.Utils: общий служебный код, используемый всеми другими проектами (минимум до 0 зависимостей).
  • App.Core: общий бизнес-код (модели, основные помощники, интерфейсы, типы).

Вариант 1: разделение модулей

  • App.Inventory: модуль инвентаризации со службами, кодом базы данных, помощниками.
  • App.Orders: модуль управления заказами со службами, базой данных, помощниками.

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

Вариант 2: разделение технического уровня

  • App.Database: функции доступа к базе данных
  • App.Services: основные реализации бизнес-сервисов.

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

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

-K

person Kishore Reddy    schedule 17.01.2017