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

46

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

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

Вы бы посоветовали позволить системе сборки создавать несколько сборок? Как я должен хранить различные настройки в управлении исходным кодом, в частности SVN?

сокол
источник
4
У #ifdefтебя работает?
Ох,
6
Директивы препроцессора могут быстро стать очень сложными и усложнить чтение и отладку кода.
Сокол
1
Вы должны включить информацию о фактической платформе и типе приложения (настольное / веб-приложение) для лучшего ответа. Настройка в настольном приложении C ++ совершенно отличается от настройки веб-приложения PHP.
GrandmasterB
2
@Falcon: с тех пор, как вы выбрали ответ в 2011 году, у меня есть много сомнений, можете ли вы сказать нам, если у вас есть какой-либо опыт использования SVN предложенным способом между ними? Являются ли мои возражения необоснованными?
Док Браун
2
@Doc Браун: ветвление и слияние было утомительным и сложным. Тогда мы использовали систему плагинов с клиентскими плагинами или «заплатками», которые изменяли поведение или конфигурации. У вас будут некоторые накладные расходы, но это можно сделать с помощью Dependendy Injection.
Сокол

Ответы:

7

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

http://svnbook.red-bean.com/en/1.5/svn.branchmerge.commonpatterns.html

Идея состоит в том, чтобы соединить кодовую ветвь с ветвями новых функций. Я имею в виду все не связанные с клиентом функции.

Кроме того, у вас также есть клиентские ветки, где вы также объединяете ветки тех же функций, что и требуется (выбор вишни, если хотите).

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

Специфичные для клиента ветви - это параллельные ветви к соединительной линии, и они активны до тех пор, пока сама соединительная линия не объединяется как единое целое.

            feature1
            ———————————.
                        \
trunk                    \
================================================== · · ·
      \ client1            \
       `========================================== · · ·
        \ client2            \
         `======================================== · · ·
              \ client2-specific feature   /
               `——————————————————————————´
Роберт Коритник
источник
7
+1 за разветвление объекта. Вы также можете использовать ветку для каждого клиента. Я хотел бы предложить только одно: использовать распределенную VCS (hg, git, bzr) вместо SVN / CVS для достижения этой цели;)
Герберт Амарал
43
-1. Это не то, для чего нужны «ветви функций»; это противоречит их определению как «временные ветви, которые объединяются, когда разработка функции закончена».
П Швед
14
Вы все еще должны поддерживать все эти дельты в управлении исходным кодом и держать их выровненными - навсегда. В краткосрочной перспективе это может сработать. В долгосрочной перспективе это может быть ужасно.
quick_now
4
-1, это не проблема именования - использование разных веток для разных клиентов - просто еще одна форма дублирования кода. Это ИМХО анти-паттерн, пример, как этого не делать.
Док Браун
7
Роберт, мне кажется, я хорошо понял, что ты предложил, еще до редактирования, но я думаю, что это ужасный подход. Предположим, у вас есть N клиентов, когда вы добавляете новую базовую функцию в транк, кажется, что SCM облегчит распространение новой функции на N ветвей. Но использование веток таким способом слишком легко избежать четкого разделения специфичных для клиента модификаций. В результате у вас теперь есть N шансов получить конфликт слияния для каждого изменения в стволе. Кроме того, теперь вам нужно запустить N интеграционных тестов вместо одного.
Док Браун
38

Не делайте этого с филиалами SCM. Сделайте общий код отдельным проектом, который создает артефакт библиотеки или скелета проекта. Каждый проект клиента является отдельным проектом, который затем зависит от общего в качестве зависимости.

Это проще всего, если ваше общее базовое приложение использует инфраструктуру внедрения зависимостей, такую ​​как Spring, так что вы можете легко внедрить различные варианты замены объектов в каждом проекте клиента, поскольку требуются пользовательские функции. Даже если у вас еще нет DI-фреймворка, добавить его и сделать это таким образом может быть наименее болезненным выбором.

стихарь
источник
Другой подход, упрощающий задачу, заключается в использовании наследования (и отдельного проекта) вместо внедрения зависимостей путем сохранения общего кода и настроек в одном и том же проекте (с использованием наследования, частичных классов или событий). При таком дизайне клиентские ветви будут работать нормально (как описано в выбранном ответе), и всегда можно обновить клиентские ветви, обновив общий код.
Дризин
Если я не пропущу что-то, вы предлагаете, чтобы, если у вас было 100 клиентов, вы создали (100 x NumberOfChangedProjects ) и использовали DI для управления ими? Если это так , что я бы определенно держаться подальше от такого решения , так как ремонтопригодность бы ужасно ..
SOTN
13

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

Модализируйте общий код и реализуйте код, который отличается в разных файлах, или защищайте его разными определениями. Сделайте так, чтобы ваша система сборки имела конкретные цели для каждого клиента, причем каждая цель собирала версию, содержащую только код, связанный с одним клиентом. Если ваш код, например, C, вы можете захотеть защитить функции для разных клиентов с " #ifdef" или любым другим механизмом, который ваш язык имеет для управления конфигурацией сборки, и подготовить набор определений, соответствующих количеству функций, за которые заплатил клиент.

Если вам не нравятся ifdefs, используйте «интерфейсы и реализации», «функторы», «объектные файлы» или любые инструменты, которые ваш язык предоставляет для хранения разных вещей в одном месте.

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

П Швед
источник
8

Как уже говорили многие: корректируйте свой код и настраивайте его на основе общего кода - это значительно улучшит удобство сопровождения. Используете ли вы объектно-ориентированный язык / систему или нет, это возможно (хотя в C это сделать немного сложнее, чем что-то действительно объектно-ориентированное). Это как раз та проблема, которую помогают решить наследование и инкапсуляция!

Майкл Трауш
источник
5

Очень осторожно

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

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

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

В результате я получил необходимый уровень настройки, я получил индивидуальные сценарии установки, так что, когда я попадаю на сайт клиента, я не выгляжу так, как будто я постоянно настраиваю систему, и в качестве дополнительного ОЧЕНЬ существенного бонуса я получаю чтобы иметь возможность создавать регрессионные тесты, подключенные непосредственно к сборке. Таким образом, в любое время, когда я получаю ошибку, специфичную для клиента, я могу написать тест, который будет утверждать систему по мере ее развертывания и, таким образом, сможет выполнять TDD даже на этом уровне.

так вкратце:

  1. Сильно модульная система с плоской структурой проекта.
  2. Создайте проект для каждого профиля конфигурации (клиент, хотя более одного может поделиться профилем)
  3. Соберите необходимые наборы функций как другой продукт и относитесь к нему как к такому.

Если все сделано правильно, ваша сборка продукта должна содержать все файлы конфигурации, кроме нескольких.

Некоторое время спустя я использовал это для создания метапакетов, которые собирают наиболее часто используемые или необходимые системы в качестве основного модуля и используют этот метапакет для пользовательских сборок. Через несколько лет у меня появился большой набор инструментов, который я мог собрать очень быстро для создания решений для клиентов. В настоящее время я изучаю Spring Roo и вижу, не смогу ли я продвинуть эту идею немного дальше, надеясь, что однажды я смогу создать первый черновик системы прямо у клиента в нашем первом интервью ... Я думаю, вы могли бы назвать его «Управляемый пользователем». Разработка ;-).

Надеюсь, это помогло

Newtopian
источник
3

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

ИМО, они не могут быть слишком маленькими. Если возможно, я бы выделил специфичный для клиента код, используя шаблон стратегии практически везде. Это уменьшит объем кода, который должен быть разветвлен, и уменьшит объединение, необходимое для синхронизации общего кода для всех клиентов. Это также упростит тестирование ... вы можете тестировать общий код, используя стратегии по умолчанию, и тестировать клиентские классы отдельно.

Если ваши модули написаны так, что использование политики X1 в модуле A требует использования политики X2 в модуле B, подумайте о рефакторинге, чтобы X1 и X2 можно было объединить в один класс политики.

Кевин Клайн
источник
1

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


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

Htbaa
источник
1

Если вы пишете на простом C, вот довольно уродливый способ сделать это.

  • Общий код (например, блок "frangulator.c")

  • специфичный для клиента код, небольшие части, используемые только для каждого клиента.

  • в коде основного модуля используйте #ifdef и #include, чтобы сделать что-то вроде

#ifdef CLIENT = CLIENTA
#include "frangulator_client_a.c"
#endif

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

Это ОЧЕНЬ некрасиво и приводит к некоторым другим неприятностям, но это также просто, и вы можете сравнительно легко сравнивать клиентские файлы один с другим.

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

Если вы действительно умны, вы можете настроить make-файлы для создания правильного определения клиента, например:

сделать клиенту

создаст для client_a, а "make clientb" сделает для client_b и так далее.

(и «make» без указания цели может выдать предупреждение или описание использования.)

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

quickly_now
источник
0

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

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

Cercerilla
источник