3

Short background: I'm using MEF/C# for developing a program that has one core (singleton) and multiple MEF parts (I call them modules, each module is a separate assembly with one class that contains the MEF export). The core imports all parts that are needed/available and initializes them (makes them read their configuration files, that stuff happens in the constructors, business as usual)

Now, my problem is that I need modules to interact with each other (say, module A, a graphical user interface, needs to get some data from a database, it'd need to somehow call a function on the database module (module B).)

How'd I do that? Sure, I could just make those parts/modules public in my core, add a reference to the core in my module and be done with it, but somehow, that'd feel ugly/bad. (mainly because I've security concerns over such an implementation, everyone could just call that function, unless I use something like this, but still it doesn't feel right).

Another approach would be to put all of those functions into the core, where they just call the corresponding function of the module. That feels bad, too because I'd have to add a lot of stuff to my core and in some cases that'd be something that a potential customer won't ever use because he doesn't need/have the specific module.

Does anyone know a better method of doing this? Maybe my train of thought went completely wrong somewhere?

Steffen Winkler
  • 905
  • 1
  • 8
  • 11

3 Answers3

4

module A, a graphical user interface, needs to get some data from a database

This is indicative of an incorrect module design.

The point of dividing the code into modules is compartmentalization of functionality. This means that each module should have a specific area of functionality, and do that only.

A GUI module should only handle the user interface.

A database module should only interact with the database.

Passing information from one to the other should happen at a higher level. This might be your core module. Or, depending on what you are trying to do, it might make sense to add another layer.

Example:

If the user clicks on a button "Get my Data", an event handler in the GUI module calls a core method requesting data. This core method then calls the database module to get the data.

  • I'm not sure how that'd be done? If $user clicks on a button 'Show some data', I've to handle the event in the class of that window/control. And I'd just do something like 'List listOfReturnedData =core.moduleB.GetMeSomeData(someParam); FillGUI(listOfReturnedData);' If you want me to do the FillGUI(listOfReturnedData); in a MVC like controller -> I'd usually do that but not here, since the data returned is (globally) known and defined in a seperate assembly and each module has to convert it the way it needs it. – Steffen Winkler Apr 24 '13 at 07:53
  • also I'm not really sure how that is relevant here, since the 'controller' would still need to somehow get the data from the core/database module. (sorry if this is worded in an offending/provocative way, it's not meant to be. Just really curious (no irony/sarcasm)) – Steffen Winkler Apr 24 '13 at 07:57
  • @SteffenWinkler, I would suggest that the GUI call a `core` method requesting data. This core method then uses the appropriate `moduleB` method to access the database. That way your database code is separate from the GUI code. If you change your database module (or even replace it with another kind of data storage), you don't have to change your GUI as well. And only the database functions that you want publicly accessible are exposed to the GUI. –  Apr 24 '13 at 07:59
  • @SteffenWinkler, it may seem like a trivial difference, but the point of modules is to add this layer of abstraction (No offense taken at your comments). –  Apr 24 '13 at 08:01
  • so, my 2nd approach is what you'd do? About changing stuff: Everything that is shared between those modules is defined in separate assemblies and the modules itself are defined by interfaces. Meaning my MEF import is something like [Import(typeof(someInterface))]. – Steffen Winkler Apr 24 '13 at 08:03
  • @SteffenWinkler basically, yes, but when it makes sense the core methods should add additional abstraction. Also, only expose the functionality that needs to be used directly in the GUI. –  Apr 24 '13 at 08:08
2

Why have a core?

I mean if everything is comprised of modules, what does the core do? Why don't your modules reference each other (via interfaces) and then use MEF to spin up concrete implementations?

Then if some component of the UI needs to access some data repository, it asks for it via Import while ignoring the rest of the module, as well as what implementation is provided. Components of your modules can then work with each other (and/or mocked variants). Just be careful to use slim interfaces and not go crazy with responsibilities and it should work well.

Telastyn
  • 108,850
  • 29
  • 239
  • 365
  • for one reason: because I like to have stuff 'organized'. another reason: because some modules are only allowed to have one instance, my core makes sure that at any given time there is only one instance. If one module would be needed by 2 others, what'd I do? Each of them would import it via MEF and create a seperate instance. (as far as I know) – Steffen Winkler Apr 24 '13 at 11:43
  • @SteffenWinkler - any _good_ reason? – Telastyn Apr 24 '13 at 11:44
  • sorry, I forgot that pressing the Enter key does not produce a line break but submits my comment. I edited it. – Steffen Winkler Apr 24 '13 at 11:45
  • also: What would the user 'start'? I need an executable (which currently is my core). And my program could also (based upon available modules) mimic a server (since I'm mono compatible even without having a GUI). Where would the executeable come from?! – Steffen Winkler Apr 24 '13 at 11:51
  • @SteffenWinkler - if you only need one instance of the module, specify shared in the MEF export. You would start whatever the 'top' module is. That's usually the UI since things tend not to depend on it, but instead it depends on other things. – Telastyn Apr 24 '13 at 13:21
  • you are correct. In case of a server, the network module substitutes the GUI and thus is the executable. Thanks. – Steffen Winkler Apr 24 '13 at 13:43
  • I'm sorry but I've to remove the answer tick for now. I looked up how that 'Shared' stuff works and as it seems, I've to put a global Container somewhere which I've to use, which would still require a core or at least an assembly that is referenced by all modules. That doesn't really solve my problem at hand, it just reduces it. For reference: http://stackoverflow.com/a/823492/937093 – Steffen Winkler Apr 25 '13 at 08:09
  • @SteffenWinkler - not a global container, a single container. And your app makes a single container when it instantiates your main module; the modules themselves should be ignorant. – Telastyn Apr 25 '13 at 11:52
  • uhm, but I need to make that container available to other modules so that those can load modules they need, so I'd need one single, global container somwhere. – Steffen Winkler Apr 25 '13 at 11:57
  • @steffenwinkler - No, I'm pretty sure as MEF cascades down the tree (A depends on B which depends on C) the same container is used to resolve B and C. Just mark B with an `import` of C and C with `export` and it just works; that's the entire point... – Telastyn Apr 25 '13 at 13:28
  • so, wait...when MEF initializes/finds a part, it'll automatically resolve the imports of that part? That'd explain this behaviour -> http://stackoverflow.com/questions/15758704/mef-importing-assemblies-in-an-imported-assemblys-constructor-does-not-work-i – Steffen Winkler Apr 26 '13 at 08:55
0

Let's talk about responsibilities.

The GUI

  • handle input from user
  • request data
  • receive requested data
  • prettify / display data

The DB access layer

  • query database
  • process queried data
  • return processed data

Additional responsibilities

  • examine data requests to verify they're not malicious. AKA scrub request for security.

There are two basic options in your case for who (which area) will handle the additional responsibilities.

  1. GUI handles scrubbing responsibilities. This keeps your DB layer clean at the risk of muddying your GUI code. In addition, it places trust within the GUI code that may not be appropriate.

  2. DBA layer handles the scrubbing responsibilities. This resolves the trust concerns but it potentially muddies up the DBA layer code.

As an alternative, consider creating an additional layer whose responsibility is to scrub incoming data requests. To facilitate the security review, I would encourage creating a specific data request object that holds the key elements you might be querying about (user ID, order numbers, date ranges, status codes, etc...).

This level of abstraction buys you a couple of notable advantages.

  • GUI no longer worries about formatting things for easy consumption by the DBA layer.
  • Scrubbing layer has a clean purpose and the security / validation code is located within the same area.
  • DBA layer can be linked to scrubbing layer and trigger security checks on the data request prior to operating upon the request.
  • Future changes to both the GUI and the DBA layer are facilitated since you now have a contract in place. Data flow looks like: GUI <=> AR <=> DBA. Future changes only have to be validated in regards to their half of the contract instead of the whole contract.

Fortunately, quite a few folk have already worked through this challenge before. The MVVM pattern ref 1, ref 2, ref 3 is worth reading for the overview and ideas on abstraction even if you don't choose to utilize the whole pattern. It may be heavier to implement than what you wish to do. MEF meshes well with this pattern, AFAIK. Pro tip: That last reference is the MS developer guide to prism and is a veritable treasure trove of information for the area you are working within.

  • well for security: it's not so much what a user could enter (adding data to my database isn't a problem. Worst case: Spam), the problem is that someone could read data (which, in my case, would be a huge security problem). – Steffen Winkler Apr 24 '13 at 11:48
  • 1
    @SteffenWinkler - so user authentication just became one of your security requirements. :-) That, actually, is the power of abstracting out the security layer of the data requests. As your program grows, it's easier to add-in new requirements. Authorization (eg. user is allowed access to _that_ data) would also be easily added in now. –  Apr 24 '13 at 11:51