如何设计一个通用的业务实体并且仍然是面向对象的?
我正在开发一种打包产品,该产品应该满足具有不同需求(在某种程度上)的多个客户,因此应该以足够灵活的方式构建,以便每个特定客户都可以进行定制。我们在这里讨论的定制类型是不同的客户端对于某些关键业务对象可能具有不同的属性。此外,它们还可以将不同的业务逻辑与其附加属性绑定在一起
作为一个非常简单的示例: 将“汽车”视为系统中的业务实体,因此有 4 个关键属性,即车辆编号、制造年份、价格和颜色。
使用系统的客户端之一可能会向 Automobile 添加另外 2 个属性,即 ChassisNumber 和 EngineCapacity。该客户端需要一些与这些字段关联的业务逻辑,以验证当添加新汽车时系统中不存在相同的chassisNumber。
另一个客户端只需要一个名为 SaleDate 的附加属性。 SaleDate 有自己的业务逻辑检查,可以在输入销售日期时验证该车辆是否在某些警察记录中不存在作为被盗车辆
我的大部分经验主要是为单个客户制作企业应用程序,并且我真的很努力地想知道如何处理一个属性是动态的业务实体,并且还具有在面向对象范式中具有动态业务逻辑的能力
关键问题
- 是否有任何通用的 OO 原则/模式可以帮助我解决这种问题设计的?
我确信从事通用/包装产品工作的人在大多数情况下都会遇到类似的情况。任何建议/指示/一般指导也将受到赞赏。
我的技术是 .NET 3.5/ C#,该项目具有分层架构,其中业务层由包含其业务逻辑的业务实体组成
I am working on a packaged product that is supposed to cater to multiple clients with varying requirements (to a certain degree) and as such should be built in a manner to be flexible enough to be customizable by each specific client. The kind of customization we are talking about here is that different client's may have differing attributes for some of the key business objects. Also, they could have differing business logic tied in with their additional attributes as well
As an very simplistic example: Consider "Automobile" to be a business entity in the system and as such has 4 key attributes i.e. VehicleNumber, YearOfManufacture, Price and Colour.
It is possible that one of the clients using the system adds 2 more attributes to Automobile namely ChassisNumber and EngineCapacity. This client needs some business logic associated with these fields to validate that the same chassisNumber doesnt exist in the system when a new Automobile gets added.
Another client just needs one additional attribute called SaleDate. SaleDate has its own business logic check which validates if the vehicle doesnt exist in some police records as a stolen vehicle when the sale date is entered
Most of my experience has been in mostly making enterprise apps for a single client and I am really struggling to see how I could handle a business entity whose attributes are dynamic and also has a capacity for having dynamic business logic as well in an object oriented paradigm
Key Issues
- Are there any general OO principles/patterns that would help me in tackling this kind of design?
I am sure people who have worked on generic / packaged products would have faced similar scenarios in most of them. Any advice / pointers / general guidance is also appreciated.
My technology is .NET 3.5/ C# and the project has a layered architecture with a business layer that consists of business entities that encompass their business logic
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
这是我们最大的挑战之一,因为我们有多个客户,它们都使用相同的代码库,但需求却千差万别。让我与您分享我们的演变故事:
我们的公司最初只有一个客户,当我们开始获得其他客户时,您会开始在代码中看到类似这样的内容:
最终我们意识到这样一个事实:丑陋且难以管理的代码。如果另一个客户希望他们的行为在一个地方像 ABC 一样,在另一个地方像 CBA 一样,我们就会陷入困境。因此,我们转而使用包含大量配置点的 .properties 文件。
这是一个改进,但仍然很笨重。 “魔弦”比比皆是。没有围绕各种属性的真正的组织或文档。许多属性依赖于其他属性,如果未以正确的组合使用,则不会执行任何操作(甚至会破坏某些内容!)。在某些迭代中,我们的大部分(甚至可能是大部分)时间都花在修复由于我们为一个客户端“修复”了破坏另一客户端配置的某些内容而出现的错误上。当我们获得一个新客户端时,我们将从另一个客户端的属性文件开始,该客户端的配置“最像”该客户端想要的配置,然后尝试进行调整,直到它们看起来正确为止。
我们尝试使用各种技术来使这些配置点变得不那么笨重,但只取得了适度的进展:
有几个项目可以控制这些配置。其中之一涉及编写一个基于 XML 的视图引擎,以便我们可以更好地为每个客户端定制显示。
另一个项目涉及编写一个配置管理系统来整合我们的配置代码,强制每个配置点都有详细记录,允许超级用户在运行时更改配置值,并允许代码验证每个更改以避免获得无效的组合配置值。
这些不同的改变无疑让每个新客户的生活变得更加轻松,但大多数都未能解决我们问题的根源。真正让我们受益最大的变化是,我们不再将我们的产品视为一系列修复程序,以使某个产品为更多客户服务,而是开始将我们的产品视为一种“产品”。当客户要求新功能时,我们开始仔细考虑这样的问题:
在实现一项功能时,我们会着眼长远。我们可以创建一个全新的表,允许任何客户端定义任意数量的自定义字段,而不是创建仅由一个客户端使用的新数据库字段。这需要更多的前期工作,但我们可以允许每个客户以极大的灵活性定制自己的产品,而不需要程序员更改任何代码。
也就是说,有时如果不在复杂的规则引擎等方面投入巨大的精力,您就无法真正完成某些自定义。当您只需要使其以一种方式为一个客户端工作,以另一种方式为另一个客户端工作时,我发现最好的选择是对接口进行编程并利用依赖注入。如果您遵循“SOLID”原则来确保您的代码是模块化编写的,具有良好的“关注点分离”等,那么为特定客户更改代码特定部分的实现就不会那么痛苦:
这我之前提到的其他技术增强了该方法。例如,我没有编写
AbcNameGenerator
因为其他客户端可能希望在他们的程序中具有类似的行为。但是使用这种方法,您可以相当轻松地定义覆盖特定客户端的默认设置的模块,方式非常灵活和可扩展。因为这样的系统本质上是脆弱的,所以重点关注自动化测试也很重要:单个类的单元测试、确保(例如)注入绑定都正常工作的集成测试,以及确保一切的系统测试一起工作而不倒退。
PS:我在整个故事中都使用“我们”,尽管我实际上并没有在这家公司工作很长时间。
PPS:请原谅 C# 和 Java 的混合。 PPS:请原谅 C# 和 Java 的混合。 EM>
This is one of our biggest challenges, as we have multiple clients that all use the same code base, but have widely varying needs. Let me share our evolution story with you:
Our company started out with a single client, and as we began to get other clients, you'd start seeing things like this in the code:
Eventually we got wise to the fact that this makes really ugly and unmanageable code. If another client wanted theirs to behave like ABC's in one place and CBA's in another place, we were stuck. So instead, we turned to a .properties file with a bunch of configuration points.
This was an improvement, but still very clunky. "Magic strings" abounded. There was no real organization or documentation around the various properties. Many of the properties depended on other properties and wouldn't do anything (or would even break something!) if not used in the right combinations. Much (possibly even most) of our time in some iterations was spent fixing bugs that arose because we had "fixed" something for one client that broke another client's configuration. When we got a new client, we would just start with the properties file of another client that had the configuration "most like" the one this client wanted, and then try to tweak things until they looked right.
We tried using various techniques to get these configuration points to be less clunky, but only made moderate progress:
There were a few projects to get these configurations under control. One involved writing an XML-based view engine so that we could better customize the displays for each client.
Another project involved writing a configuration management system to consolidate our configuration code, enforce that each configuration point was well documented, allow super users to change the configuration values at run-time, and allow the code to validate each change to avoid getting an invalid combination of configuration values.
These various changes definitely made life a lot easier with each new client, but most of them failed to address the root of our problems. The change that really benefited us most was when we stopped looking at our product as a series of fixes to make something work for one more client, and we started looking at our product as a "product." When a client asked for a new feature, we started to carefully consider questions like:
When implementing a feature, we would take the long view. Rather than creating a new database field that would only be used by one client, we might create a whole new table which could allow any client to define any number of custom fields. It would take more work up-front, but we could allow each client to customize their own product with a great degree of flexibility, without requiring a programmer to change any code.
That said, sometimes there are certain customizations that you can't really accomplish without investing an enormous effort in complex Rules engines and so forth. When you just need to make it work one way for one client and another way for another client, I've found that your best bet is to program to interfaces and leverage dependency injection. If you follow "SOLID" principles to make sure your code is written modularly with good "separation of concerns," etc., it isn't nearly as painful to change the implementation of a particular part of your code for a particular client:
This approach is enhanced by the other techniques I mentioned earlier. For example, I didn't write an
AbcNameGenerator
because maybe other clients will want similar behavior in their programs. But using this approach you can fairly easily define modules that override default settings for specific clients, in a way that is very flexible and extensible.Because systems like this are inherently fragile, it is also important to focus heavily on automated testing: Unit tests for individual classes, integration tests to make sure (for example) that your injection bindings are all working correctly, and system tests to make sure everything works together without regressing.
PS: I use "we" throughout this story, even though I wasn't actually working at the company for much of its history.
PPS: Pardon the mixture of C# and Java.
这是一个动态对象模型或自适应对象模型。当然,当客户开始添加行为和数据时,他们正在编程,因此您需要为此进行版本控制、测试、发布、命名空间/上下文和权限管理。
That's a Dynamic Object Model or Adaptive Object Model you're building. And of course, when customers start adding behaviour and data, they are programming, so you need to have version control, tests, release, namespace/context and rights management for that.
解决此问题的一种方法是使用元层或反射,或两者兼而有之。此外,您将需要提供一个定制应用程序,允许用户修改您的业务逻辑层。这样的元层并不真正适合您的分层架构 - 它更像是与您现有架构正交的层,尽管正在运行的应用程序可能需要引用它,至少在初始化时是这样。这种类型的设施可能是人类已知的搞砸生产应用程序的最快方法之一,因此您必须:
您的元层将包含业务对象、方法、属性等对象以及添加业务对象、调用方法等事件。
网络上有大量有关元编程的信息,但我将从模式开始程序设计语言第 2 卷或任何与 Kent 或 Coplien 相关或源自 Kent 或 Coplien 的 WWW 资源。
A way of approaching this is to use a meta-layer, or reflection, or both. In addition you will need to provide a customisation application which will allow modification, by the users, of your business logic layer. Such a meta-layer does not really fit in your layered architecture - it is more like a layer orthoganal to your existing architecture, though the running application will probably need to refer to it, at least on initialisation. This type of facility is probably one of the fastest ways of screwing up the production application known to man, so you must:
Your meta-layer will have objects in it such as Business Object, Method, Property and events such as Add Business Object, Call Method etc.
There is a wealth of information about meta-programming available on the web, but I would start with Pattern Languages of Program Design Vol 2 or any of the WWW resources related to, or emanating from Kent or Coplien.
我们开发了一个 SDK 来完成类似的事情。我们选择 COM 作为我们的核心,因为我们对它比使用低级 .NET 更舒服,但毫无疑问,您可以在 .NET 中本地完成这一切。
基本架构是这样的:类型在 COM 类型库中描述。所有类型都派生自名为 Object 的根类型。 COM DLL 实现此根对象类型,并通过 IDispatch 提供对派生类型属性的通用访问。该 DLL 封装在 .NET PIA 程序集中,因为我们预计大多数开发人员更喜欢在 .NET 中工作。对象类型有一个工厂方法来创建模型中任何类型的对象。
我们的产品处于版本 1,我们尚未实现方法 - 在此版本中,业务逻辑必须编码到客户端应用程序中。但我们的总体愿景是,方法将由开发人员用他选择的语言编写,编译为 .NET 程序集或 COM DLL(也可能是 Java)并通过 IDispatch 公开。然后我们的根对象类型中的相同 IDispatch 实现可以调用它们。
如果您预计大多数自定义业务逻辑将被验证(例如检查重复的底盘编号),那么您可以在根对象类型上实现一些常规事件(假设您按照我们的方式进行操作)。我们的对象类型每当更新属性时都会触发一个事件,我想这可以通过一种验证方法来增强,如果定义了该方法,该方法会自动调用。
创建这样的通用系统需要做大量工作,但回报是基于 SDK 的应用程序开发速度非常快。
您说您的客户应该能够“无需编程”即可添加自定义属性并自行实现业务逻辑。如果您的系统还实现了基于类型的数据存储(我们的系统是这样做的),那么客户可以通过编辑模型(我们提供 GUI 模型编辑器)来添加属性,而无需编程。您甚至可以提供一个通用的用户应用程序,动态地呈现适当的属性。根据类型进行数据输入控制,因此您的客户无需额外编程即可捕获自定义数据。 (我们提供了一个通用的客户端应用程序,但它更多的是一个开发人员工具,而不是一个可行的最终用户应用程序。)我不明白如何允许您的客户无需编程即可实现自定义逻辑......除非您想提供某种拖放式 GUI 工作流程构建器...无疑是一项艰巨的任务。
我们不希望商业用户做任何这些事情。在我们的开发模型中,所有定制均由开发人员完成,但不一定是昂贵的 - 我们的愿景之一是允许经验不足的开发人员生成强大的业务应用程序。
We develop an SDK that does something like this. We chose COM for our core because we were far more comfortable with it than with low-level .NET, but no doubt you could do it all natively in .NET.
The basic architecture is something like this: Types are described in a COM type library. All types derive from a root type called Object. A COM DLL implements this root Object type and provides generic access to derived types' properties via IDispatch. This DLL is wrapped in a .NET PIA assembly because we anticipate that most developers will prefer to work in .NET. The Object type has a factory method to create objects of any type in the model.
Our product is at version 1 and we haven't implemented methods yet - in this version business logic must be coded into the client application. But our general vision is that methods will be written by the developer in his language of choice, compiled to .NET assemblies or COM DLLs (and maybe Java too) and exposed via IDispatch. Then the same IDispatch implementation in our root Object type can call them.
If you anticipate that most of the custom business logic will be validation (such as checking for duplicate chassis numbers) then you could implement some general events on your root Object type (assuming you did it something like the way we do.) Our Object type fires an event whenever a property is updated, and I suppose this could be augmented by a validation method that gets called automatically if one is defined.
It takes a lot of work to create a generic system like this, but the payoff is that application development on top of the SDK is very quick.
You say that your customers should be able to add custom properties and implement business logic themselves "without programming". If your system also implements data storage based on the types (ours does) then the customer could add properties without programming, by editing the model (we provide a GUI model editor.) You could even provide a generic user application that dynamically presents the appropriate data-entry controls depending on the types, so your customers could capture custom data without additional programming. (We provide a generic client application but it's more a developer tool than a viable end-user application.) I don't see how you could allow your customers to implement custom logic without programming... unless you want to provide some kind of drag-n-drop GUI workflow builder... surely a huge task.
We don't envisage business users doing any of this stuff. In our development model all customisation is done by a developer, but not necessarily an expensive one - part of our vision is to allow less experienced developers produce robust business applications.
设计一个充当其自己的独立项目的核心模型
以下是一些可能的基本要求的列表...
核心设计将包含:
然后,每个客户定制的所有后续项目都被视为该核心项目的扩展。
您所描述的是任何框架的基本目的。也就是说,创建一组可以与整体分开的核心功能,这样您就不必在创建的每个项目中重复该开发工作。也就是说,放入一个框架,你的工作就已经完成了一半。
您可能会说,“那SCM(软件配置管理)呢?”
如何在不将核心包含到子项目存储库中的情况下跟踪所有子项目的修订历史记录?
幸运的是,这是一个老问题。许多软件项目,尤其是 Linux/开源世界中的软件项目,广泛使用外部库和插件。
事实上 git 有一个命令专门用于将一个项目存储库作为子存储库导入另一个项目存储库(保留子存储库的所有修订历史记录等)。事实上,您无法修改子存储库的内容,因为该项目根本不会跟踪它的历史记录。
我所说的命令称为“git submodule”。
您可能会问,“如果我在一个客户的项目中开发了一项非常酷的功能,并且希望在所有客户的项目中使用,该怎么办?”。
只需将该功能添加到核心并在所有其他项目上运行“git 子模块同步”即可。 git 子模块的工作方式是,它指向子存储库历史树中的特定提交。因此,当上游更改该树时,您需要将这些更改拉回下游到使用它们的项目。
实现这样的事情的结构将像这样工作。假设您的软件是专门为管理汽车经销商(库存、销售、员工、客户、订单等)而编写的。您创建一个涵盖所有这些功能的核心模块,因为它们预计将在所有客户的软件中使用。
但是,您最近获得了一位新客户,他希望通过向经销商增加在线销售来提高技术水平。当然,他们的网站是由 Web 开发人员/设计师和网站管理员组成的独立团队设计的,但他们希望有一个 Web API(即服务层)来利用其网站的当前基础设施。
您要做的就是为客户端创建一个项目,我们将其称为 WebDealersRUs 并将核心子模块链接到存储库中。
这样做的隐藏好处是,一旦您开始将代码库视为可插入部件,您就可以从一开始就将它们设计为能够轻松放入项目中的模块化部件。
考虑上面的例子。假设您的客户群开始看到添加网络前端以增加销售额的优点。只需将 Web API 从 WebDealersRU 中拉出到其自己的存储库中,并将其作为子模块链接回来即可。然后传播给所有需要它的客户。
您所获得的就是以最小的努力获得最大的回报。
当然,每个项目中总会有一些特定于客户的部分(品牌等)。这就是为什么每个客户都应该拥有一个单独的存储库,其中包含其独特的软件版本。但这并不意味着您不能将各个部分抽出来并进行概括以在后续项目中重复使用。
虽然我从宏观层面解决这个问题,但它可以应用于代码库的更小/更具体的部分。这里的关键是您希望重用的代码需要通用化。
OOP 在这里发挥作用,因为: 当功能在核心中实现但在客户端代码中扩展时,您将使用基类并从中继承;如果功能预计返回类似类型的结果,但该功能的实现在类之间可能有很大不同(即,没有直接的继承层次结构),最好使用接口来强制这种关系。
Design a core model that acts as its own independent project
Here's a list of some possible basic requirements...
The core design would contain:
Then, all of the subsequent projects that are customized per client are considered extensions of this core project.
What you're describing is the basic purpose of any Framework. Namely, create a core set of functionality that can be set apart from the whole so you don't have to duplicate that development effort in every project you create. Ie, drop in a framework and half your work is done already.
You might say, "what about the SCM (Software Configuration Management)?"
How do you track revision history of all of the subprojects without including the core into the subproject repository?
Fortunately, this is an old problem. Many software projects, especially those in the the linux/open source world, make extensive use of external libraries and plugins.
In fact git has a command that's specifically used to import one project repository into another as a sub-repository (preserving all of the sub-repository's revision history etc). In fact, you can't modify the contents of the sub-repository because the project won't track it's history at all.
The command I'm talking about is called 'git submodule'.
You may ask, "what if I develop a really cool feature in one client's project that I'd like to use in all of my client's projects?".
Just add that feature to the core and run a 'git submodule sync' on all the other projects. The way git submodule works is, it points to a specific commit within the sub-repository's history tree. So, when that tree is changed upstream, you need to pull those changes back downstream to the projects where they're used.
The structure to implement such a thing would work like this. Lets say that you software is written specifically to manage a car dealership (inventory, sales, employees, customers, orders, etc...). You create a core module that covers all of these features because they are expected to be used in the software for all of your clients.
But, you have recently gained a new client who wants to be more tech savvy by adding online sales to their dealership. Of course, their website is designed by a separate team of web developers/designers and webmaster but they want a web API (Ie, service layer) to tap into the current infrastructure for their website.
What you'd do is create a project for the client, we'll call it WebDealersRUs and link the core submodule into the repository.
The hidden benefit of this is, once you start to look as a codebase as pluggable parts, you can start to design them from the start as modular pieces that are capable of being dropped in to a project with very little effort.
Consider the example above. Lets say that your client base is starting to see the merits of adding a web-front to increase sales. Just pull the web API out of the WebDealersRUs into its own repository and link it back in as a submodule. Then propagate to all of your clients that want it.
What you get is a major payoff with minimal effort.
Of course there will always be parts of every project that are client specific (branding, ect). That's why every client should have a separate repository containing their unique version of the software. But that doesn't mean that you can't pull parts out and generalize them to be reused in subsequent projects.
While I approach this issue from the macro level, it can be applied to smaller/more specific parts of the codebase. The key here is code that you wish to re-use needs to be genericized.
OOP comes into play here because: where the functionality is implemented in the core but extended in client's code you'll use a base class and inherit from it; where the functionality is expected to return a similar type of result but the implementations of that functionality may be wildly different across classes (Ie, there's no direct inheritance hierarchy) it's best to use an interface to enforce that relationship.
我知道你的问题很笼统,与技术无关,但既然你提到你实际上使用 .NET,我建议你看一下 .NET 4 的一个新的、非常重要的技术部分: '动态'类型。
这里还有一篇关于 CodeProject 的好文章:DynamicObjects – .NET 中的 Duck-Typing。
它可能值得一看,因为,如果我必须实现您描述的动态系统,我肯定会尝试基于 DynamicObject 类实现我的实体,并使用 TryGetxxx 方法添加自定义属性和方法。它还取决于您是关注编译时还是运行时。这里有一个有趣的链接:动态添加成员到动态对象 关于这个主题。
I know your question is general, not tied to a technology, but since you mention you actually work with .NET, I suggest you look at a new and very important technology piece that is part of .NET 4: the 'dynamic' type.
There is also a good article on CodeProject here: DynamicObjects – Duck-Typing in .NET.
It's probably worth to look at, because, if I have to implement the dynamic system you describe, I would certainly try to implement my entities based on the DynamicObject class and add custom properties and methods using the TryGetxxx methods. It also depends whether you are focused on compile time or runtime. Here is an interesting link here on SO: Dynamically adding members to a dynamic object on this subject.
我的感觉有两种方法:
1)如果不同的客户属于同一领域(如制造/金融),那么最好以这样的方式设计对象,即 BaseObject 应该具有非常常见的属性,而其他属性可能因客户而异作为键值对。除此之外,尝试实现像IBM ILog(http://www-01.ibm.com/software/integration/business-rule-management/rulesnet-family/about/)这样的规则引擎。
2) 预测模型标记语言(http://en.wikipedia.org/wiki/PMML)
Two approaches is what I feel:
1) If different clients fall on to same domain (as Manufacturing/Finance) then it's better to design objects in such a way that BaseObject should have attributes which are very common and other's which could vary in between clients as key-value pairs. On top of it, try to implement rule engine like IBM ILog(http://www-01.ibm.com/software/integration/business-rule-management/rulesnet-family/about/).
2) Predictive Model Markup Language(http://en.wikipedia.org/wiki/PMML)