1

I'm making a program that supports plugins. The program is being written in F#.

The program maintains a single object that represents the state of the program. When a plugin is invoked, it takes the state and returns a new state which replaces the old one.

Example: The filereader plugin takes an empty state and puts the contents of a specified file into the state. The textviewer plugin takes this state, checks if it contains a "text" object; if it does, it displays the text on the screen.

In this way, a filereader can be implemented seperately from the textviewer. The textviewer only cares if there is a "text" object in the state; it does not care how the object got there.

My question is of implementation. I could represent the state as a big JSON object. But this is rather clumsy (but it isn't too bad). I could also use System.Object to hold arbitrary data, but is this a good approach?

2 Answers 2

2

First, you probably want to use Event Driven architecture in this case, or something similar to decouple all the modules. Once module is done doing what it's doing, it triggers an event on the main program that all other modules are subscribed to.

Now, to your question: I would only store the references to resources provided by the modules in the state - not the whole text file as you described.

  1. Text Reader Module is attached to main program.
  2. Module is updating the state with new reference to the text file that it stores somewhere.
  3. Text loaded event is dispatched.
  4. Text Viewer is subscribed to the event, and thus pulls the file by reference from the state.

Your state object should represent a collection of assets probably utilizing Decorator pattern and potentially Composite pattern to treat collections of collections as just assets if required.

That's roughly what I would do. Of course, this would change a bit as you design and implement as I am making a lot of assumptions here.

1

Plugin architectures is quite a well-known problem in Object-Oriented Design, where it's usually addressed by defining a set of interfaces that the host program expects the plugins to implement.

On .NET, the Managed Extensibility Framework was explicitly designed to address this problem. Not only does it support interfaces that are defined in advance by the host program, but it can also discover and compose interfaces that one plugin requires (imports), and another plugin implements (exports).

Other mature DI Containers that support convention-based Auto-registration can be configured to supply the same features.

In F#, substitutability and composition is usually achieved with functions instead of interfaces, but I'm not aware of any DI Containers or other composition engines that work on F# functions instead of .NET interfaces. Since I mostly favour Pure DI these days, I also hope never to see this happen.

You may wonder why this is relevant in an F# context, but once you realise that there's a strong relationship between objects and closures, the relevancy may become more apparent.

In any case, the architecture suggested here isn't how plugin architectures are normally implemented. As explained above, plugin architectures are usually defined around a set of predefined behaviours, rather than data. Well-designed interfaces define behaviours, just as functions do. These are strongly typed and substitutable (polymorphic).

Weakly typed data is likely to become a maintenance headache over time, so I wouldn't go with that design.

You can read more about (Object-Oriented) loosely coupled architectures in my book.

You don't write about your root cause for writing an application with a plug-in architecture, but unless you're writing a big shrink-wrapped application like Visual Studio, a browser, a general-purpose web server, or the like, this sort of architecture is most likely overkill.

One of the best features of F# is that it prohibits cycles. In Object-Oriented languages like C# and Java, people often utilise component-based architectures like Ports and Adapters, Onion architecture, layers, etc because at the module level, cycles are prevented (you can't easily create cycles with hard dependencies). However, in F#, you get this assistance from the language itself, so there's much less reason to address coupling with architecture; coupling is already addressed by the language.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.