Как вы управляете несколькими версиями одного и того же программного обеспечения для каждого клиента? - PullRequest
23 голосов
/ 07 июля 2010

У меня есть исходный код, который на 95% одинаков для всех клиентов. Однако некоторые клиенты просят что-то конкретное. Как я могу управлять этим, возможно ли это с VisualSVN / Subversion?

Обновление:

Некоторые подробности о приложении, это веб-ASP.NET MVC с NHibernate.

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

Сервисный проект использует проект репо, а сервисный проект - это проект с бизнес-правилами.

Ответы [ 7 ]

11 голосов
/ 07 июля 2010

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

Первый включает ответвление кода для каждого клиента. Любое редактирование, которое вы делаете в главной строке, может быть интегрировано в ветку конкретного клиента, когда это необходимо. Точно так же, если что-то в основном продукте зафиксировано в филиале, его можно объединить обратно в основную линию для последующего распространения в филиалах других клиентов. Хотя это может показаться лучшим подходом, может быть сложно поддерживать и отслеживать, какая ветвь имеет какие правки будут чреваты.

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

5 голосов
/ 07 июля 2010

Подход, который мы выбрали:

  • Вставка ловушек внутри приложения, позволяющая настроить поведение по умолчанию (например, когда вызывается действие Save, первое, что происходит внутри, - это вызов OnSaveHandler).
  • Обработчик по умолчанию ничего не делает, он просто возвращает «continueWithNormalExecution». Все обработчики находятся в другом модуле, чем исходное приложение (другая сборка), назовем его BehaviourModule
  • В клиентских запросах мы модифицируем BehaviourModule, переопределяя стандартное поведение «ничего не делать». Код возврата этого модифицированного обработчика может быть: ContinueNormalExecution, SkipNormalExecution, TerminateExecution и т. Д. *
  • В других случаях мы вставляем хуки на основе интерфейсов. В BehaviourModule у нас будет больше обработчиков, реализующих этот интерфейс, например, DoStuffInterface, BehaviourModule анализируется во время загрузки с использованием отражения, и все обработчики, реализующие DoStuffInterface, будут зарегистрированы в системе. Тогда в исходном приложении у нас будет что-то вроде: Если GetDoStuffInterfaceHandler(handlerID) isnot Nothing, то GetDoStuffInterfaceHandler(handlerID).DoStuff(). Определение используемого handlerId настраивается (может быть через таблицу db, файл xml и т. Д.).

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

При таком подходе имеем:

  • базовое приложение с поведением по умолчанию
  • настраиваемый модуль (сборка) настраивает способ работы приложения

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

Надеюсь, я был понятен в своем описании, если нет ... оставьте комментарий:)

5 голосов
/ 07 июля 2010

Поместите специфичный для клиента код в отдельные проекты / сборки. Что-то вроде шаблона стратегии или плагинов может быть подходящим вариантом.

Другим менее привлекательным способом (IMO) было бы создание отдельных филиалов для каждого клиента, но это может быстро стать трудно поддерживаемым.

1 голос
/ 07 июля 2010

Полезное дополнение к #ifdef ACME / # endif и т. Д. - определение макросов для макросов ACME_ONLY (), NON_ACME (), FROBOZCO_ONLY (), NON_FROBOZCO () и т. Вещи могут все еще запутаться, если новые версии вступают в игру (в этом случае новая версия должна вести себя как Acme, FrobozCo и т. Д.), Но если есть только одна линия различия между версией Acme и не-Acme, этот подход позволяет избежать строка из двух строк директив #.

1 голос
/ 07 июля 2010

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

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

0 голосов
/ 07 июля 2010

Использование контроля версий для решения этой проблемы, вероятно, вызовет больше проблем, чем решит.

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

Другой вариант - использовать # if ... # endif .

#if CustomerA

    ... do x ...

#else

    ... do y ...

#endif

Вам нужно будет настроить ваши сценарии сборки для создания конкретных пользовательских двоичных файлов. например:

msbuild mysolution.sln /property:DefineConstants="CustomerA"

msbuild mysolution.sln /property:DefineConstants="CustomerB"
0 голосов
/ 07 июля 2010

Разница в 5% в том, что только на основе пользовательского интерфейса или бизнес-логики? Если на основе пользовательского интерфейса, чем вы должны задействовать уровень пользовательского интерфейса и отправить / скомпилировать соответствующий файл пользовательского интерфейса с приложением. Если бизнес-логика, это сложнее. Возможно ветвление (через SVN) могло бы помочь. Но по-прежнему возникают проблемы с текущей разработкой приложения, поэтому не рекомендуется.

...