Подтвердить что ты не робот

Как создать общий бизнес-объект и все еще быть OO?

Я работаю над упакованным продуктом, который, как предполагается, обслуживает несколько клиентов с различными требованиями (в определенной степени) и как таковой должен быть построен таким образом, чтобы быть достаточно гибким, чтобы настраиваться каждым конкретным клиентом. Такого рода настройка, о которой мы говорим здесь, заключается в том, что у разных клиентов могут быть разные атрибуты для некоторых ключевых бизнес-объектов. Кроме того, они могут иметь другую бизнес-логику, связанную с их дополнительными атрибутами, а также

В качестве очень упрощенного примера: Рассмотрим "Автомобиль" как бизнес-объект в системе и как таковой имеет 4 ключевых атрибута, то есть VehicleNumber, YearOfManufacture, Price и Color.

Возможно, что один из клиентов, использующих систему, добавляет еще 2 атрибута к Automobile, а именно ChassisNumber и EngineCapacity. Для этого клиента требуется определенная бизнес-логика, связанная с этими полями, чтобы проверить, что одно и то же имя chassisNumber не существует в системе при добавлении нового Automobile.

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

Большая часть моего опыта заключалась в основном создании корпоративных приложений для одного клиента, и я действительно изо всех сил стараюсь понять, как я могу обрабатывать бизнес-объект, атрибуты которого являются динамическими, а также имеет возможность иметь динамическую бизнес-логику, а также объектно-ориентированная парадигма

Основные проблемы

  • Существуют ли какие-либо общие принципы/шаблоны OO, которые помогут мне в решении такого рода проектов?

Я уверен, что люди, которые работали с продуктами общего типа/упакованными продуктами, столкнулись бы с похожими сценариями в большинстве из них. Любые советы/указатели/общее руководство также приветствуются.

Моя технология -.NET 3.5/С#, а проект имеет многоуровневую архитектуру с бизнес-уровнем, состоящим из бизнес-единиц, которые охватывают их бизнес-логику.

4b9b3361

Ответ 1

Это одна из наших самых больших проблем, так как у нас есть несколько клиентов, которые используют одну и ту же базу кода, но имеют разные потребности. Позвольте мне поделиться с вами своей эволюционной историей:

Наша компания начала работать с одним клиентом, и по мере того как мы начали привлекать других клиентов, вы начали бы видеть такие вещи в коде:

if(clientName == "ABC") {
    // do it the way ABC client likes
} else {
    // do it the way most clients like.
}

В конце концов мы получили мудрый факт, что это делает действительно уродливый и неуправляемый код. Если другой клиент хотел, чтобы они вели себя как ABC в одном месте, а CBA в другом месте, мы застряли. Поэтому вместо этого мы обратились к файлу .properties с кучей точек конфигурации.

if((bool)configProps.get("LastNameFirst")) {
    // output the last name first
} else {
    // output the first name first
}

Это было улучшением, но все еще очень неуклюжим. "Волшебные струны" изобиловали. Не было никакой реальной организации или документации вокруг различных свойств. Многие свойства зависели от других свойств и ничего не предпринимали (или даже что-то сломали!), Если они не используются в правильных комбинациях. Многое (возможно, даже большинство) нашего времени в некоторых итерациях было потрачено на исправление ошибок, которые возникли из-за того, что мы "исправили" что-то для одного клиента, который нарушил другую конфигурацию клиента. Когда у нас появился новый клиент, мы просто начнем с файла свойств другого клиента, у которого была конфигурация "наиболее похожа" на тот, которую хотел этот клиент, а затем попытаться настроить все, пока они не выглядят правильно.

Мы попытались использовать различные методы, чтобы эти точки конфигурации были менее неуклюжими, но только сделали умеренный прогресс:

if(userDisplayConfigBean.showLastNameFirst())) {
    // output the last name first
} else {
    // output the first name first
}

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

<client name="ABC">
    <field name="last_name" />
    <field name="first_name" />
</client>

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

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

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

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

Тем не менее, иногда есть определенные настройки, которые вы не можете выполнить, не вкладывая огромных усилий в сложные механизмы правил и т.д. Когда вам просто нужно заставить его работать одним способом для одного клиента и другим способом для другого клиента, я обнаружил, что лучше всего использовать программу для интерфейсов и инъекции зависимостей рычагов. > . Если вы следуете принципам "SOLID", чтобы убедиться, что ваш код написан модульно с хорошим "разделением проблем" и т.д., Не так сложно изменить реализацию определенной части вашего кода для конкретного клиента:

public FirstLastNameGenerator : INameDisplayGenerator
{
    IPersonRepository _personRepository;
    public FirstLastNameGenerator(IPersonRepository personRepository)
    {
        _personRepository = personRepository;
    }
    public string GenerateDisplayNameForPerson(int personId)
    {
        Person person = _personRepository.GetById(personId);
        return person.FirstName + " " + person.LastName;
    }
}

public AbcModule : NinjectModule
{
     public override void Load()
     {
         Rebind<INameDisplayGenerator>().To<FirstLastNameGenerator>();
     }
}

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

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

PS: Я использую "мы" на протяжении всей этой истории, хотя я на самом деле не работал в компании на протяжении большей части своей истории.

PPS: Простите смесь С# и Java.

Ответ 2

Это Динамическая объектная модель или Адаптивная объектная модель вы строите. И, конечно, когда клиенты начинают добавлять поведение и данные, они программируются, поэтому для этого вам необходимо иметь контроль версий, тесты, выпуск, пространство имен/контекст и управление правами.

Ответ 3

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

  • Убедитесь, что доступ к этому редактору ограничен людьми с высоким уровнем прав в системе (например, администратором).
  • Предоставить область песочницы для модификаций клиента, которые будут проверяться до того, как все изменения, которые они тестируют, будут помещены в производственную систему.
  • Объект "OOPS", с помощью которого они могут вернуть свою производственную систему либо в исходное значение по умолчанию, либо в последнюю версию перед изменением.
  • Ваш мета-слой должен быть очень жестко задан, чтобы определить диапазон действий - Джордж Оруэлл "Что конкретно не разрешено, запрещено".

В вашем метаслое будут объекты, такие как бизнес-объект, метод, свойство и события, такие как Добавить бизнес-объект, метод вызова и т.д.

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

Ответ 4

Мы разрабатываем SDK, который делает что-то подобное. Мы выбрали COM для нашего ядра, потому что нам было намного комфортнее, чем с низкоуровневым .NET, но, без сомнения, вы могли бы все это сделать в .NET.

Базовая архитектура выглядит примерно так: Типы описаны в библиотеке типа COM. Все типы производятся из корневого типа Object. COM DLL реализует этот корневой тип объекта и предоставляет общий доступ к свойствам производных типов через IDispatch. Эта DLL завернута в сборку .NET PIA, потому что мы ожидаем, что большинство разработчиков предпочтут работать в .NET. Тип объекта имеет метод factory для создания объектов любого типа в модели.

Наш продукт находится в версии 1, и мы еще не реализовали методы - в этой версии бизнес-логика должна быть закодирована в клиентское приложение. Но наше общее видение заключается в том, что методы будут написаны разработчиком на его собственном языке, скомпилированном в сборках .NET или COM-библиотеках (и, возможно, в Java), и будут показаны через IDispatch. Тогда такая же реализация IDispatch в нашем корневом типе объектов может вызывать их.

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

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

Вы говорите, что ваши клиенты должны иметь возможность добавлять пользовательские свойства и сами внедрять бизнес-логику "без программирования". Если ваша система также реализует хранилище данных на основе типов (наш), то клиент может добавлять свойства без программирования, редактируя модель (мы предоставляем редактор модели графического интерфейса). Вы даже можете предоставить общее пользовательское приложение, которое динамически представляет соответствующие элементы управления вводом данных в зависимости от типов, поэтому ваши клиенты могут захватывать пользовательские данные без дополнительного программирования. (Мы предоставляем общее клиентское приложение, но это скорее инструмент разработчика, чем жизнеспособное приложение конечного пользователя.) Я не вижу, как вы могли бы позволить своим клиентам внедрять пользовательскую логику без программирования... если вы не хотите предоставлять какие-либо drag-n-drop GUI workflow builder... безусловно, огромная задача.

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

Ответ 5

Создайте базовую модель, которая действует как собственный независимый проект

Вот список некоторых возможных основных требований...

Основной проект будет содержать:

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

Затем все последующие проекты, настроенные для каждого клиента, считаются расширениями этого основного проекта.

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


Вы можете сказать: "Как насчет SCM (Управление конфигурацией программного обеспечения)?"

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

К счастью, это старая проблема. Многие программные проекты, особенно в мире linux/open source, широко используют внешние библиотеки и плагины.

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

Команда, о которой я говорю, называется git подмодуль.

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

Просто добавьте эту функцию в ядро ​​и запустите 'git subodule sync' для всех других проектов. Как работает подмодуль git, он указывает на конкретную фиксацию в дереве истории суб-репозитория. Таким образом, когда это дерево изменяется вверх по течению, вам нужно потянуть эти изменения обратно вниз к проектам, в которых они используются.

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

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

Что бы вы сделали, так это создать проект для клиента, мы будем называть его WebDealersRU и связывать основной подмодуль в репозитории.


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

Рассмотрим приведенный выше пример. Допустим, что ваша клиентская база начинает видеть преимущества добавления веб-сайта для увеличения продаж. Просто вытащите веб-API из WebDealersRUs в свой собственный репозиторий и свяжите его обратно в качестве подмодуля. Затем распространяйте все ваши клиенты, которые этого хотят.

То, что вы получаете, является основным выигрышем с минимальными усилиями.


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

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

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

Ответ 6

Я знаю, что ваш вопрос вообще, не привязан к технологии, но поскольку вы упоминаете, что работаете на .NET, я предлагаю вам взглянуть на новую и очень важную часть технологии, которая является частью .NET 4: тип "dynamic" .

Здесь также есть хорошая статья о CodeProject: DynamicObjects - Duck-Typing в .NET.

Возможно, стоит посмотреть, потому что, если мне нужно реализовать динамическую систему, которую вы описываете, я бы, конечно же, попытался реализовать мои объекты на основе класса DynamicObject и добавить пользовательские свойства и методы с помощью методов TryGetxxx. Это также зависит от того, сосредоточены ли вы на времени компиляции или времени выполнения. Вот интересная ссылка здесь на SO: Динамическое добавление участников к динамическому объекту по этому вопросу.

Ответ 7

Я чувствую два подхода:

1) Если разные клиенты попадают в один и тот же домен (например, Manufacturing/Finance), тогда лучше проектировать объекты таким образом, чтобы BaseObject должен иметь атрибуты, которые очень распространены, и другие, которые могут варьироваться между клиентами как ключевыми пар. Кроме того, попробуйте реализовать механизм правил, например IBM ILog (http://www-01.ibm.com/software/integration/business-rule-management/rulesnet-family/about/).

2) Предиктивный язык разметки модели (http://en.wikipedia.org/wiki/PMML)