Как создать общий бизнес-объект и при этом оставаться объектно-ориентированным?

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

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

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

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

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

Ключевые вопросы

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

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

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


person Jagmag    schedule 29.01.2011    source источник
comment
Должны ли ваши клиенты иметь возможность добавлять атрибуты и реализовывать настраиваемую бизнес-логику или вам нужна возможность добавить эти адаптации для конкретных клиентов?   -  person Andreas Vendel    schedule 29.01.2011
comment
@Andreas - Бывший. Заказчики должны иметь возможность делать это сами динамически, без программирования изменений.   -  person Jagmag    schedule 29.01.2011
comment
@All - ответы Криса и Кьярана полезны по-своему, но если я их правильно понимаю, оба включают архитектурные изменения для всей архитектуры приложения, и, поскольку мое - существующее приложение, для которого это требование является скорее улучшением, я был бы признателен любые другие шаблоны / решения ООП, которые, возможно, легче вписываются в мой существующий подход N-Layer. Следовательно, назначаем награду за этот вопрос, чтобы, возможно, привлечь к нему внимание.   -  person Jagmag    schedule 03.02.2011
comment
Одновременно только один клиент использует эту систему или несколько клиентов?   -  person Jijoy    schedule 10.02.2011


Ответы (7)


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

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

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>

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

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

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

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

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

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: Простите за смесь C # и Java.

person StriplingWarrior    schedule 08.02.2011
comment
Все эти «если», почему бы вам не попробовать OO :) - person Stephan Eggermont; 08.02.2011
comment
@Stephan Eggermont: Да, вы заметите, что по мере развития продукта мы в конечном итоге отказались от многих операторов if в пользу конфигурации XML / базы данных или внедрения зависимостей. Конечно, хотя хорошие методы объектно-ориентированного программирования часто позволяют избежать if чрезмерного использования, объектно-ориентированный объект по своей сути не является анти-if (stackoverflow.com/questions/1167589/anti-if-campaign/) - person StriplingWarrior; 09.02.2011

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

person Stephan Eggermont    schedule 07.02.2011

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

  1. Убедитесь, что доступ к этому редактору ограничен людьми с высоким уровнем прав в системе (например, администратором).
  2. Предоставьте область песочницы для тестирования модификаций клиентов, прежде чем любые изменения, которые они тестируют, будут внесены в производственную систему.
  3. Средство «OOPS», с помощью которого они могут вернуть свою производственную систему либо к предоставленному вами начальному значению по умолчанию, либо к последней версии перед изменением.
  4. Ваш мета-слой должен быть очень строго определен, чтобы четко определить диапазон действий - Джордж Оруэлл: «Что конкретно не разрешено, то запрещено».

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

В сети имеется огромное количество информации о метапрограммировании, но я бы начал с Pattern Languages ​​of Program Design Vol 2 или любых ресурсов WWW, связанных или исходящих от Kent или Coplien.

person Chris Walton    schedule 29.01.2011
comment
Спасибо за ссылки на мета-программирование. Это определенно то, о чем я не очень разбираюсь, и мне нужно будет рассмотреть более подробно. - person Jagmag; 30.01.2011

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

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

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

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

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

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

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

person Ciaran Keating    schedule 30.01.2011
comment
Спасибо за подробный ответ. Это определенно помогло мне и дало мне несколько возможных идей для изучения. - person Jagmag; 30.01.2011

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

Вам нужно создать проект для клиента, мы назовем его WebDealerRUs и свяжем основной подмодуль с репозиторием.


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

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

Вы получаете большую отдачу с минимальными усилиями.


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

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

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

person Evan Plaice    schedule 09.02.2011


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

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

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

person Community    schedule 06.02.2011
comment
Для базы данных пар "ключ-значение", которая постоянно хранит значения на извлеченном диске memcachedb.org. Другие такие реализации базы данных (а также основанные на использовании ключей памяти, а не диска) можно найти в конце этой статьи - en.wikipedia.org/wiki/NoSQL. - person Evan Plaice; 09.02.2011