扩展/插件通信的架构
一旦解决了加载插件的问题(在 .NET 中,在例外情况下通过 MEF),下一步要解决的是与它们的通信。简单的方法是实现一个接口并使用插件实现,但有时插件只需要扩展应用程序的工作方式,并且可能有很多扩展点。
我的问题是关于如何处理这些扩展点。我已经看到了不同的方法,但我不确定每种方法的优缺点,以及是否有更多更好的方法来实现这一目标:
- 事件:将静态事件添加到我们想要“可扩展”的所有内容中”。例如,如果我想为 User 类添加自定义验证,我可以添加 OnValidation 静态事件处理程序,并在构建插件时向其添加事件。
- 消息传递:拥有总线和消息。该插件可以订阅特定消息,并在其他类发布该消息时执行某些操作。该消息应包含插件可以工作的上下文。在验证情况下,逻辑层将发布 UserValidation 消息,插件将在收到消息时采取行动。
- 接口:主机应用程序负责调用所有实现某些接口的插件,并为它们提供当前操作的上下文。在验证的情况下,插件可以使用 Validate(object context) 方法实现 IValidator 或 IUserValidator。
您是否曾经使用过其中一种公开的方法?哪一种最适合您?
在您提问之前,我们的应用程序是一个可扩展的核心(用户、角色和内容管理),可在此基础上构建我们的客户特定的以内容为中心的 Web 应用程序。一切都构建在 ASP.NET MVC 上。
Once the problem of loading plugins is solved (in .NET through MEF in out case), the next step to solve is the communication with them. The simple way is to implement an interface and use the plugin implementation, but sometimes the plugin just needs to extend the way the application works and there may be a lot of extension points.
My question is about how to deal with that extension points. I've seen different ways of doing that but I'm not sure of the pros and cons of each one and if there are more and better ways to accomplish this:
- Events: Adding static events to all the stuff we want to make "extendable". For example, if I want to add custom validation for a User class, I can add a OnValidation static event handler and add events to it from the plugin when it's constructed.
- Messaging: Having a bus and a messages. The plugin can subscribe to a particular message and do stuff when some other class publishes that message. The message should contain the context in which the plugin can work. In the validation case, the logic layer will publish a UserValidation message and the plugin will act when the message is received.
- Interfaces: The host application is responsible of calling all the plugins that implement certain interfaces and give them the context of the current operation. In the case of validation, the plugin may implement a IValidator or IUserValidator with a Validate(object context) method.
Have you ever user one of the exposed approaches? Which one worked best for you?
And before you ask, our application is an extensible core (user, rola and content management) to build our client specific content centric web applications on top of that. Everything built on ASP.NET MVC.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
设计决策的关键是分析并清楚地了解插件之间的差异。
例如,在处理静态事件时,您可能必须将每个事件定义为某种形式的令牌、枚举、对象等。必须为每个插件定义一组新的事件自然不利于您的整个设计,特别是在松散耦合方面并重复利用。
如果您的插件非常不同,您可能会受益于总线/消息传递架构,因为在这种情况下您可以引入插件可以订阅的通信交换的域/类别。即,一系列事件和消息可以处于某个兴趣域中。请注意,特定类别内的通信仍然可以利用静态事件,因此这两种选择并不相互排斥。
根据我的经验,插件实现的直接接口是插件架构中最严格的方法。扩展插件接口通常意味着插件和提供者的代码修改。您需要有一个可靠的通用界面,您知道您的应用程序可以运行相当长的一段时间。
通过将设计分解为两个方面——通信渠道和协议,您可能会更轻松地处理设计。静态事件处理是一个协议问题,而总线消息传递和直接接口是一个通道问题。
一般来说,我会说协议是最难从一开始就正确设计的,因为您可能对如何划定界限没有明确的感觉。
编辑: Lars 在他的评论中提出了一个重要观点 - 如果您的平台支持异常,那么您可以在使用直接接口时集中处理大量错误,从而使插件不必处理通用和错误的错误。也许超出了他们的特定域(例如“插件加载错误”或“文件打开失败”)。然而,如果每次添加插件时都必须维护接口,那么这些好处似乎就会消失。最糟糕的情况是接口开始变得不一致,因为您从一开始就没有意识到它们应该支持什么。当已经构思出大量插件时,重构整个界面设计并不是一件容易的事。
A key for your design decision is to analyze and get a clear picture of how different the plugins will be from eachother.
E.g. when dealing with static events, you will probably have to define each event as some form of token, enum, object etc. Having to define a new set of events for each plugin naturally works against your whole design, particularly in terms of loose coupling and reuse.
If your plugins are very different you might benefit from having a bus/messaging architecture since you in such case can introduce domains/categories of communication exchange, which the plugins can subscribe to. I.e. a range of events and messages can be in a certain interest domain. Note here that communication within a certain category can still utilize static events, so those two alternatives are not mutually exclusive.
Direct interfaces implemented by the plugins is in my experience the strictest approach of plugin architecture. Extending the plugin interface usually implies code modification at both plugin and provider. You need to have a solid general interface which you know your application can live on for quite some time.
It may be easier for you to deal with the design by breaking it down into two aspects - communication channel and protocol. Static event handling is a protocol issue, while bus-messaging and direct interfaces is a channel issue.
Generally I would say that the protocol is the hardest to design correctly from the beginning, since you may not have a solid feel for how general or specific you can draw the line.
EDIT: Lars made an important point in his comment - if your platform supports exceptions, you can centralize a lot of the error handling when using direct interfaces, relieving the plugins from having to handle errors that are generic and perhaps outisde their particular domain (e.g. "plugin load error", or "file open failed"). However, such benefits will seem to fade if you have to maintain interfaces each time you add plugins. Worst case is when the interfaces start becoming inconsistent along the way because you didn't realize what they should support from the beginning. Refactoring the entire interface design when a substantial amount of plugins already have been conceived is not an easy task.
我会选择观察者模式。来自 GOF:
也称为发布-订阅,我建议它与示例中的情况二最匹配。
I'd go with the Observer pattern. From the GOF:
Also known as publish-subscribe I would suggest it most closely matches case two in your examples.