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

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

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

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

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

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

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

  • есть ли общие принципы/шаблоны OO, которые помогли бы мне в решении такого рода дизайна?

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

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

7 ответов


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

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

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

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

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.


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


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

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

в вашем мета-слое будут объекты, такие как бизнес-объект, метод, свойство и события, такие как Add Business Object, Call Method и т. д.

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


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

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

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

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

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

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

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


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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

Что вы хотели do-это создать проект для клиента, мы назовем его WebDealersRUs и свяжем основной подмодуль в репозиторий.


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

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

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


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

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

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


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

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

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


два подхода - это то, что я чувствую:

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

2) Прогностическая Модель Язык разметки(http://en.wikipedia.org/wiki/PMML)