Как спроектировать общий бизнес-объект и все еще быть OO? - PullRequest
18 голосов
/ 29 января 2011

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

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

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

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

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

Ключевые проблемы

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

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

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

Ответы [ 7 ]

13 голосов
/ 08 февраля 2011

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

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

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

2 голосов
/ 08 февраля 2011

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

1 голос
/ 09 февраля 2011

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

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

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

1 голос
/ 09 февраля 2011

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

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

Дизайн ядра будет содержать:

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

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

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


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

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

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

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

Команда, о которой я говорю, называется ' git submodule '.

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

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

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

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

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


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

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

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


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

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

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

1 голос
/ 30 января 2011

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

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

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

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

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

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

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

1 голос
/ 30 января 2011

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

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

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

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

0 голосов
/ 06 февраля 2011

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

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

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

...