Jump to content

How do I avoid coupling modules together by data type?


Recommended Posts

Hi All,

I'm writing (what I hope is nice) modular code, trying to modularise functions and UIs etc accross the applications we're developing to keep things nice and flexible. But I'm struggling to keep individual modules "uncoupled" - or rather not all interdependent on each other. 

Example - I have module for communicating with an oscilloscope to capture waveforms. I create a typedef in that module for passing out those waveforms and related information to another module, which sends them on to a storage module and a waveform processing module (where I do various things to the raw waveform) and then finally out to a waveform display module... 

So my question becomes "who should own the waveform data typedef" - the easiest thing to do is make sure the input side of any module's API accepts the output of the API producing the data... so if it sits in the acquisition module library at the "source" of the data, then assuming I dont keep translating to different data types, all of my other modules end up dependent back to there too - e.g. my waveform processing and display modules now depend on the acquisition module - but I might want to write a post processing and display application that needs to know how to process waveforms, but I dont want to have to include the waveform acquisition module with that as it wont be acquiring anything...

I'm sure there's lots of options, and this post is really to try and find some I havent thought of yet!

I know I could create the typedef in it's own library or even outside of any library, then the users of that typdef are not dependent on each other (I think that's basically a form of dependency inversion?). But then that gets hard to manage and leads to the idea of a "common data types" library, which can quickly grow to lots of other things and more types of coupling in a way.

I could translate the typedef as it passes through a chain of modules so that each module defines the way it expects to receive the data, and then a tree type module hierarchy limits the coupling... but that always feels somewhat inefficient - and I want one waveform data type, not 4 depending on where I am in the application.

My situation is marginally complicated as well since we're using PPLs and some of my data types are now classes, so they need to be inside a PPL somewhere and then used from there (and its a pain we cant build a packed class without first wrapping it in a project library...)

So yes - how do you all manage your typedefs and classes that get used across module boundaries to minimise those dependency issues!? 

 

Thanks in advance, sorry for the slightly rambly post!

  • Like 2
Link to comment

You're right that there are a few ways to do this.  Having a separate library of Common Controls is one way to go about it.  In this you only need to have Type Defs that are shared between two or more libraries.  For me the overwhelming majority of the time a Type Def is only needed in one library.  If I have a set of VIs that does some kind of thing and needs a cluster or enum I'll save it as a type def to go along with the library.  Any library that needs to use that type def, is going to probably need it because it will be a constant, or a control, that calls a VI from the first library.  And so in that case it doesn't really matter that the second library depends on the first because of the control, because it is going to call the subVI so that dependency requirement is going to be there anyway.  So I'll just have one type def control in the first library.

Sometimes I'll make separate Type Defs, that contain the same data in different libraries, but in the future they might not.  So one library may have an enum with "Yes, No, Cancel" and named "User Response.ctl".  Even though another library has the need for the same set of enum values, I might make one separate control for each library because while they contain the same data, they aren't directly related.  This could also work for you.  Even though the two libraries have the same data Waveform data, they could be decoupled.  Wiring will then do an automatic coercion dot which is nice, but could lead to issues in some small cases.

  • Like 1
Link to comment

Thanks for the feedback, that's helpful.

Related question (convolution!) - where we have a git repo for each "module", suppose we then need to consider a repo of "data type libraries" (there could be more than one to allow for groupings of different data types...).

It all just feels unnecessarily complex somehow, but I guess thats the problem with dependency management, to keep the dependencies between modules "simple" and reduce coupling, it seems you need relative complex strategies for managing the dependencies! Anyone have a feel for whether that's a general thing among other languages or is it particularly challenging in LabVIEW?

Thanks again!

Link to comment

It is a huge challenge in LabVIEW. It is often overlooked by beginners which causes lots of problems.

I often just have a common folder on disk full of typedefs.

I have tried to use more generic types as much as possible when transferring data across module boundaries. If I can I pass some native datatype like a map, set or array. For a waveform, why not just a simple native waveform? If you have metadata, IIRC waveforms accept metadata just like variants do.

Occassionally, I'll even pass a simple cluster without making it a typedef. ie. an x-y point is just a cluster of 2 doubles. No real need to make that a typedef. Of course as soon as it gets any more complicated, then you probably need a typedef.

I've also given a lot of thought to using JSON to pass data between modules, but haven't quite worked that out yet.

Link to comment
  • 1 month later...

it’s a general programming problem that exists in textual languages too although without the additional problem of tying components in the same strict manner together as with LabVIEW strict typedefs used in code inside different PPLs.

But generally you need to have a strict hierarchical definition of typedefs in programming languages like C too or you rather sooner than later end in header dependency hell too. More modern programming languages tried to solve that by dynamic loading and linking of code at runtime, which has it’s own difficulties.

LabVIEW actually does that too but at the same time it also has a strict type system that it enforces at compile time, and in that way mixes some of the difficulties from both worlds.

One possible solution in LabVIEW is to make for all your data classes and pass them around like that. That’s very flexible but also very heavy handed. It’s also easily destroying performance if you aren’t very careful how you do it. One of the reason LabVIEW can be really fast is because it works with native data types, not some more or less complex data type wrappers.

Edited by Rolf Kalbermatter
Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.