Я работаю над большим программным проектом, который специально адаптирован для различных клиентов по всему миру. Это означает, что у нас может быть 80% кода, который является общим для разных клиентов, но также и много кода, который должен меняться от одного клиента к другому. В прошлом мы занимались разработкой в отдельных репозиториях (SVN), и когда начинался новый проект (у нас мало, но крупных клиентов), создавался другой репозиторий, основанный на том, какой предыдущий проект имел наилучшую основу кода для наших нужд. Это работало в прошлом, но мы столкнулись с несколькими проблемами:
- Ошибки, которые исправлены в одном репозитории, не исправляются в других репозиториях. Это может быть проблемой организации, но мне трудно исправить и исправить ошибку в 5 разных репозиториях, учитывая, что команда, обслуживающая этот репозиторий, может находиться в другой части мира, и у нас нет тестовой среды также не знаете своего графика или требований, которые у них есть («ошибка» в одной стране может быть «функцией» в другой).
- Функции и улучшения, сделанные для одного проекта, которые также могут быть полезны для другого проекта, теряются или если они используются в другом проекте, часто вызывают большие головные боли при слиянии их из одной базы кода в другую (поскольку обе ветви могли разрабатываться независимо в течение года ).
- Рефакторинг и улучшения кода, сделанные в одной ветви разработки, либо теряются, либо приносят больше вреда, чем пользы, если вам нужно объединить все эти изменения между ветвями.
Сейчас мы обсуждаем, как решить эти проблемы, и до сих пор придумали следующие идеи, как решить эту проблему:
Сохраняйте разработку в отдельных ветках, но лучше организуйте, имея центральный репозиторий, в который объединяются общие исправления ошибок, и все проекты объединяют изменения из этого центрального репозитория в свои собственные на регулярной (например, ежедневной) основе. Это требует огромной дисциплины и больших усилий для слияния ветвей. Так что я не уверен, что это сработает, и мы сможем сохранить эту дисциплину, особенно, когда время ограничено.
Отказаться от отдельных ветвей разработки и иметь центральное хранилище кода, где живет весь наш код, и выполнять нашу настройку, имея подключаемые модули и параметры конфигурации. Мы уже используем контейнеры Dependency Injection для разрешения зависимостей в нашем коде, и мы следуем шаблону MVVM в большей части нашего кода, чтобы четко отделить бизнес-логику от нашего пользовательского интерфейса.
Второй подход кажется более элегантным, но у нас есть много нерешенных проблем в этом подходе. Например: как обрабатывать изменения / дополнения в вашей модели / базе данных. Мы используем .NET с Entity Framework, чтобы иметь строго типизированные сущности. Я не понимаю, как мы можем обрабатывать свойства, которые требуются для одного клиента, но бесполезны для другого клиента, не загромождая нашу модель данных. Мы думаем о решении этого в базе данных с помощью спутниковых таблиц (имеющих отдельные таблицы, в которых наши дополнительные столбцы для конкретной сущности живут с отображением 1: 1 на исходную сущность), но это только база данных. Как вы справляетесь с этим в коде? Наша модель данных находится в центральной библиотеке, которую мы не сможем расширить для каждого клиента, использующего этот подход.
Я уверен, что мы не единственная команда, борющаяся с этой проблемой, и я шокирован, когда нашел так мало материала по этой теме.
Итак, мои вопросы следующие:
- Какой у вас опыт работы с высоко настраиваемым программным обеспечением, какой подход вы выбрали и как он работал для вас?
- Какой подход вы рекомендуете и почему? Есть ли лучший подход?
- Есть ли хорошие книги или статьи на эту тему, которые вы можете порекомендовать?
- У вас есть конкретные рекомендации для нашей технической среды (.NET, Entity Framework, WPF, DI)?
Редактировать:
Спасибо за все предложения. Большинство идей совпадают с теми, которые у нас уже были в нашей команде, но очень полезно увидеть опыт, который вы имели с ними, и советы, как лучше их реализовать.
Я все еще не уверен, по какому пути мы пойдем, и я не принимаю решение (в одиночку), но я передам это в моей команде, и я уверен, что это будет полезно.
На данный момент тенор, похоже, представляет собой единый репозиторий с использованием различных пользовательских модулей. Я не уверен, что наша архитектура соответствует этому или сколько мы должны вложить, чтобы привести его в соответствие, поэтому некоторые вещи могут некоторое время жить в отдельных репозиториях, но я думаю, что это единственное долгосрочное решение, которое будет работать.
Итак, еще раз спасибо за все ответы!
Ответы:
Похоже, основная проблема заключается не только в обслуживании хранилища кода, но и в отсутствии подходящей архитектуры .
Фреймворк или стандартная библиотека включает в себя первый, тогда как последний будет реализован как надстройки (плагины, подклассы, DI, все, что имеет смысл для структуры кода).
Система управления исходным кодом, которая управляет филиалами и распределенной разработкой, вероятно, также поможет; Я фанат Mercurial, другие предпочитают Git. Каркас будет основной ветвью, например, каждая настроенная система будет дочерней.
Конкретные технологии, используемые для реализации системы (.NET, WPF и т. Д.), В значительной степени не важны.
Получить это правильно нелегко , но это важно для долгосрочной жизнеспособности. И, конечно, чем дольше вы ждете, тем больше технической задолженности вам придется иметь дело.
Вы можете найти полезной книгу « Архитектура программного обеспечения: организационные принципы и шаблоны» .
Удачи!
источник
Одна компания, в которой я работал, имела ту же проблему, и подход к решению этой проблемы заключался в следующем: была создана общая структура для всех новых проектов; это включает в себя все вещи, которые должны быть одинаковыми в каждом проекте. Например, инструменты для создания форм, экспорт в Excel, ведение журнала. Усилия были предприняты, чтобы убедиться, что эта общая структура только улучшена (когда новый проект нуждается в новых функциях), но никогда не раздваивались.
Основываясь на этой структуре, специфичный для клиента код поддерживался в отдельных репозиториях. Когда это полезно или необходимо, исправления ошибок и улучшения вставляются между проектами (со всеми оговорками, описанными в вопросе). Однако полезные в глобальном масштабе улучшения входят в общую структуру.
Наличие всего в единой кодовой базе для всех клиентов имеет некоторые преимущества, но, с другой стороны, чтение кода становится трудным, когда существует бесчисленное множество параметров,
if
чтобы заставить программу вести себя по-разному для каждого клиента.РЕДАКТИРОВАТЬ: Один анекдот, чтобы сделать это более понятным:
источник
Один из проектов, над которым я работал, поддерживал несколько платформ (более 5) в большом количестве выпусков продукта. Многие проблемы, которые вы описываете, были вещами, с которыми мы столкнулись, хотя и немного по-другому. У нас была собственная БД, поэтому у нас не было проблем такого же типа на этой арене.
Наша структура была похожа на вашу, но у нас был один репозиторий для нашего кода. Специфичный для платформы код помещался в собственные папки проекта в дереве кода. Общий код жил в дереве в зависимости от того, к какому слою он принадлежал.
У нас была условная компиляция, основанная на создаваемой платформе. Поддержание этого было болезненным, но это нужно было делать только тогда, когда на уровне платформы были добавлены новые модули.
Наличие всего кода в одном репозитории позволило нам легко исправлять ошибки на разных платформах и выпусках одновременно. У нас была автоматизированная среда сборки для всех платформ, которая могла бы служить защитой на случай, если новый код сломает предполагаемую несвязанную платформу.
Мы пытались препятствовать этому, но были случаи, когда платформе требовалось исправление, основанное на ошибке, специфичной для платформы, которая была в другом общем коде. Если бы мы могли условно переопределить компиляцию, не заставляя модуль выглядеть беспорядочно, мы сделали бы это в первую очередь. Если нет, мы бы переместили модуль из общей территории и вставили его в платформу.
Для базы данных у нас было несколько таблиц, которые имели специфичные для платформы столбцы / модификации. Мы бы позаботились о том, чтобы каждая версия таблицы платформы соответствовала базовому уровню функциональности, чтобы общий код мог ссылаться на него, не беспокоясь о зависимостях платформы. Специфичные для платформы запросы / манипуляции были перенесены в уровни проекта платформы.
Итак, чтобы ответить на ваши вопросы:
источник
Я много лет работал над заявлением в пенсионное управление, в котором были похожие проблемы. Пенсионные планы сильно различаются между компаниями и требуют узкоспециализированных знаний для реализации логики расчетов и отчетов, а также очень разного дизайна данных. Я могу только дать краткое описание части архитектуры, но, возможно, она даст достаточно идеи.
У нас было 2 отдельные команды: основная команда разработчиков , которая отвечала за код основной системы (который будет вашим кодом на 80% выше), и команда внедрения , которая обладала знаниями в области пенсионных систем и отвечала за обучение клиентов. требования и кодирование скриптов и отчетов для клиента.
Мы определили все наши таблицы в Xml (это было до того времени, когда структуры сущностей были проверены временем и стали общепринятыми). Группа разработчиков спроектирует все таблицы в Xml, а ядро приложения может получить запрос на создание всех таблиц в Xml. Для каждого клиента были также связаны файлы сценариев VB, отчеты Crystal Reports, документы Word и т. Д. (В Xml также была встроена модель наследования, позволяющая повторно использовать другие реализации).
Базовое приложение (одно приложение для всех клиентов) будет кэшировать все специфичные для клиента вещи при поступлении запроса для этого клиента и генерирует общий объект данных (вроде как набор записей удаленного ADO), который можно сериализовать и передать около.
Эта модель данных менее удобна, чем объекты сущностей / доменов, но она очень гибкая, универсальная и может обрабатываться одним набором базового кода. Возможно, в вашем случае вы могли бы определить базовые объекты сущности только с общими полями и иметь дополнительный словарь для настраиваемых полей (добавить некоторый набор дескрипторов данных к вашему объекту сущности, чтобы у него были метаданные для настраиваемых полей. )
У нас были отдельные репозитории для исходного кода системы и кода реализации.
Наша базовая система на самом деле имела очень мало бизнес-логики, за исключением некоторых очень стандартных общих модулей расчета. Основная система функционировала как: экранный генератор, скрипт-бегун, генератор отчетов, доступ к данным и транспортный уровень.
Сегментация базовой и настраиваемой логики - сложная задача. Тем не менее, мы всегда чувствовали, что лучше иметь одну базовую систему с несколькими клиентами, а не несколько копий системы, запущенной для каждого клиента.
источник
Я работал над небольшой системой (20 килокалорий) и обнаружил, что DI и конфигурация являются отличными способами управления различиями между клиентами, но их недостаточно, чтобы избежать разветвления системы. База данных разделена между определенной частью приложения, которая имеет фиксированную схему, и зависимой от клиента частью, которая определяется через пользовательский документ конфигурации XML.
Мы сохранили одну ветку в Mercurial, настроенную так, как если бы она была доставляемой, но фирменную и настроенную для вымышленного клиента. Исправления ошибок включены в этот проект, и новые разработки основных функциональных возможностей происходят только там. Релизы для реальных клиентов - это ветки, хранящиеся в их собственных репозиториях. Мы отслеживаем большие изменения в коде с помощью написанных вручную номеров версий и отслеживаем исправления ошибок, используя номера коммитов.
источник
Боюсь, что у меня нет прямого опыта проблемы, которую вы описываете, но у меня есть некоторые комментарии.
Второй вариант - собрать код в центральном хранилище (насколько это практически возможно) и создать архитектуру для настройки (опять же, насколько это практически возможно) - почти наверняка путь в долгосрочной перспективе.
Проблема в том, как вы планируете туда добраться и сколько времени это займет.
В этой ситуации, вероятно, нормально (временно) иметь более одной копии приложения в хранилище одновременно.
Это позволит вам постепенно перейти к архитектуре, которая напрямую поддерживает настройку, не делая это одним махом.
источник
Я уверен, что любая из этих проблем может быть решена одна за другой. Если вы застряли, спросите здесь или на SO о конкретной проблеме.
Как уже отмечали другие, вам следует выбрать одну центральную кодовую базу / один репозиторий. Я пытаюсь ответить на ваш пример вопроса.
Есть некоторые возможности, все они я видел в реальных системах. Какой из них выбрать, зависит от вашей ситуации:
ввести таблицы «CustomAttributes» (описывающие имена и типы) и «CustomAttributeValues» (для значений, например, хранящихся в виде строкового представления, даже если они являются числами). Это позволит добавлять такие атрибуты во время установки или выполнения, имея индивидуальные значения для каждого клиента. Не настаивайте на том, чтобы каждый пользовательский атрибут моделировался «визуально» в вашей модели данных.
теперь должно быть понятно, как использовать это в коде: иметь только общий код для доступа к этим таблицам и отдельный код (возможно, в отдельной подключаемой библиотеке DLL, которая зависит от вас) для правильной интерпретации этих атрибутов
И для конкретного кода: вы также можете попробовать ввести язык сценариев в свой продукт, особенно для добавления пользовательских сценариев. Таким образом, вы не только создаете четкую грань между вашим кодом и кодом, специфичным для клиента, вы также можете позволить своим клиентам настраивать систему в некоторой степени самостоятельно.
источник
Я только построил одно такое приложение. Я бы сказал, что 90% проданных единиц были проданы без изменений. У каждого клиента был свой собственный скин, и мы обслуживали систему в этом скине. Когда появился мод, который затронул основные разделы, мы попытались использовать IF ветвление . Когда в том же разделе появился мод № 2, мы переключились на логику CASE, которая позволяла расширяться в будущем. Это, казалось, обрабатывало большинство второстепенных запросов.
Любые дальнейшие незначительные пользовательские запросы обрабатывались с помощью логики Case.
Если моды были двухрадикальными, мы создали клон (отдельно включенный) и обернули вокруг него CASE, чтобы включить другой модуль.
Исправления ошибок и модификации ядра затрагивали всех пользователей. Мы тщательно протестировали в разработке, прежде чем идти в производство. Мы всегда отправляли уведомления по электронной почте, которые сопровождали любые изменения, и НИКОГДА, НИКОГДА, НИКОГДА не публиковали производственные изменения по пятницам ... НИКОГДА.
Нашей средой был классический ASP и SQL Server. Мы НЕ были магазином спагетти-кода ... Все было модульно с использованием Включений, Подпрограмм и Функций.
источник
Когда меня попросят начать разработку B, который разделяет функциональность на 80% с A, я либо:
Вы выбрали 1, и это, кажется, не соответствует вашей ситуации. Ваша миссия состоит в том, чтобы предсказать, какие из 2 и 3 лучше подходят.
источник