Как вы управляете несколькими версиями одного и того же программного обеспечения для каждого клиента?
У меня есть исходный код, который на 95% одинаковый для всех клиентов. Однако некоторые клиенты просят что-то конкретное. Как я могу управлять этим, возможно ли это с помощью VisualSVN/Subversion?
Обновление:
Некоторые подробности о приложении, это веб-ASP.NET MVC с NHibernate.
Приложение имеет несколько проектов: веб-часть, часть репо (где мы используем NHibernate для доступа к базе данных) и проект службы.
Проект службы использует проект репо, а проект службы - это проект с бизнес-правилами.
Ответы
Ответ 1
Я могу думать о двух подходах, которые могут сработать.
Первое включает разветвление кода для каждого клиента. Любое редактирование, которое вы делаете в основной строке, затем может быть интегрировано в конкретную ветвь клиента, когда это необходимо. Аналогично, если что-то в основном продукте фиксируется в ветке, его можно объединить обратно в основную линию для последующего распространения на ветки других клиентов. Хотя это может показаться наилучшим подходом, его может быть сложно поддерживать и отслеживать, какая ветвь имеет, какие изменения будут чреваты.
Второй и, возможно, лучший подход включает в себя рефакторинг вашего кода, чтобы конкретный код клиента находился в одной сборке - по одному на каждого клиента. Затем он настраивается, возможно, с помощью инъекции зависимостей, когда продукт установлен. Таким образом, у вас есть только одна строка кода и не слияние ветвей. Хотя он полагается на то, что клиентский код легко отделяется.
Ответ 2
Поместите конкретный код клиента в отдельные проекты/сборки. Возможно, что-то вроде шаблона стратегии или плагинов.
Другим менее привлекательным способом (ИМО) было бы создание отдельных ветвей для каждого клиента, но это может быстро усложниться.
Ответ 3
Подход, который мы сделали, заключается в следующем:
- Вставьте крючки внутри приложения, позволяющие настраивать поведение по умолчанию (например, когда вызывается действие
Save
, первое, что происходит внутри, - это вызов OnSaveHandler
).
- Обработчик по умолчанию ничего не делает, он просто возвращает "continueWithNormalExecution". Все обработчики находятся в другом модуле, чем исходное приложение (другая сборка), позвоните ему
BehaviourModule
- В клиентских запросах мы модифицируем этот
BehaviourModule
, переопределяя значение по умолчанию "ничего не делайте". Код возврата этого модифицированного обработчика может быть: ContinueNormalExecution
, SkipNormalExecution
, TerminateExecution
и т.д.
-
В других случаях мы вставляем крючки на основе интерфейсов. В BehaviourModule
у нас будет больше обработчиков, реализующих этот интерфейс, например. DoStuffInterface
, BehaviourModule
анализируется во время загрузки с использованием отражения, и все обработчики, реализующие DoStuffInterface
, будут регистрироваться в системе.
Тогда в исходном приложении у нас будет что-то вроде: If GetDoStuffInterfaceHandler(handlerID) isnot Nothing
then GetDoStuffInterfaceHandler(handlerID).DoStuff()
. Определение того, какой handlerId использовать, настраивается (может быть через таблицу db, xml файл и т.д.).
В итоге у нас есть несколько обработчиков, реализующих DoStuffInterface
с разными идентификаторами и вызывающих их в разное время.
При таком подходе мы имеем:
- базовое приложение со стандартным поведением
- настраиваемый модуль (сборка) настраивает способ работы приложения.
Проблема с этим подходом заключается в поиске "сладких точек" - поведения, которое клиент может захотеть настроить и вставить туда крючки.
Надеюсь, что в моем описании было ясно, если не... оставить комментарий:)
Ответ 4
Если это неважно, я бы пошел с настройкой appp и шаблоном factory. Или конкретные сборки для каждого клиента.
Но из тегов вы хотите решить его с помощью контроля версий. Но это сильно повлияет на слияние и т.д.
Вам нужно будет создать ветвь для каждого клиента и слить изменения с внешней стороны на них.
Ответ 5
Полезным дополнением к #ifdef ACME/# endif и т.д. является определение макросов для макросов ACME_ONLY(), NON_ACME(), FROBOZCO_ONLY(), NON_FROBOZCO() и т.д. Материал может по-прежнему запутываться, если в игру вступают новые версии (в каких случаях должна вестись новая версия, например, Acme, FrobozCo и т.д.), Но если есть только одна разница между версией Acme и non-Acme, этот подход избегает строка двумя строками из # директив.
Ответ 6
Разница в 5% заключается в том, что только на основе пользовательского интерфейса или бизнес-логики?
Если пользовательский интерфейс основан на том, что вы должны отменить уровень пользовательского интерфейса и отправить/скомпилировать соответствующий файл пользовательского интерфейса с приложением.
Если бизнес-логика, это сложнее. Возможно, ветвление (через SVN) может помочь. Но все же хлопот с текущей разработкой приложения, поэтому не рекомендуется.
Ответ 7
Использование контроля версий для решения этой проблемы, вероятно, вызовет больше проблем, чем решает.
Предложения других пользователей о разделении конкретного кода клиента на отдельные сборки и/или использование инъекции зависимостей - один из способов.
Другой вариант - использовать # if... #endif.
#if CustomerA
... do x ...
#else
... do y ...
#endif
Вам нужно будет настроить скрипты сборки для создания конкретных клиентских двоичных файлов. например:
msbuild mysolution.sln /property:DefineConstants="CustomerA"
msbuild mysolution.sln /property:DefineConstants="CustomerB"