best practise - using data from other module

best practise - using data from other module

  

Hi,

I was wondering what is best practise and why for the following situation:

I need to expose data for a drop down in a field in a form. This data comes from a different espace.

possibility a (red in the attached example): I load the data directly from the aggregate from the other espace (making the data publicly available) or

solution b (green in the attached example): I expose the data to the drop down from the core of its own espace with a function, the core of the espace then fetches data from the other espace by calling a function.

To me it seems better practise to go for the second solution, but I wonder what are the advantages and disadvantages of both.


thanks.

Hi,

Probably depends on the reasons to separate data in different modules/applications in the first place. I'll call the first application A (that consumes data) and the second application B (that produces data), and I'll call modules End-User A/B and Core A/B accordingly.

Does the Core A need the data from the Core B? If not, why implement an extra Action in Core A that just acts as an indirection to Core B and add the extra dependency?

Would there be safeguards as to who/when data from Core B can be accessed? if so, then you may want to consider an Action on Core B, but you could probably call it directly from End-User A.

In general, for reads, you're probably better off using an Aggregate directly from End-User A that fetches data made Public (but most likely Expose Read-Only) in Core B. This will allow for extra optimisation by the platform when called from the Preparation, for instance. 

Jorge Martins wrote:

Hi,

Probably depends on the reasons to separate data in different modules/applications in the first place. I'll call the first application A (that consumes data) and the second application B (that produces data), and I'll call modules End-User A/B and Core A/B accordingly.

Does the Core A need the data from the Core B? If not, why implement an extra Action in Core A that just acts as an indirection to Core B and add the extra dependency?

Would there be safeguards as to who/when data from Core B can be accessed? if so, then you may want to consider an Action on Core B, but you could probably call it directly from End-User A.

In general, for reads, you're probably better off using an Aggregate directly from End-User A that fetches data made Public (but most likely Expose Read-Only) in Core B. This will allow for extra optimisation by the platform when called from the Preparation, for instance. 

Hi, thanks for your feedback. the point is that A needs to fetch data from B. Otherwise there is indeed no point in having this set up. it is a lot easier to use the aggregate from B directly in A then creating functions to fetch the data via the cores. I was just wondering if this is good practise.


I am noticing using the solution b approach that it causes significant lag in fetching data. The user experiences lag in filling the drop down. Drop down is also filled after the user changes a different field which means the user has to wait till drop down is filled. This did not occur in the solution a.

What I meant by "Does the Core A need the data from the Core B?" was: does the Core module of A need the data from B? or is it only going to be used by the Front-End module of A?

Using Aggregates directly in the Preparation is actually considered a best practice. But it all depends on your requirements.

what are "solution a" and "solution b"?

Jorge Martins wrote:

what are "solution a" and "solution b"?

solution a = possibility a, solution b is solution b. the data is not needed in the core of module a, however it will be updated depending on the value from a different field the user has entered.


Hi Johnny, it’s a good question. In general, I would say that option c would be preferable as you would encapsulate the manner in which core module B stores the data. By option c I mean that the entity in the core module B would not be exposed, not even read only. In addition, the end-user module consumes directly from the core module B but using an action instead of the aggregate. There is no point in adding a dependency from core module A to core module B. By not exposing the entity, we get more encapsulation, that decreases dependencies between modules, I.e. stimulates “low coupling” and low coupling is one of the cornerstones of good software.

In the specific case of OutSystems, the platform helps you in managing dependencies between modules. If you change one module, the platform will let you know whether all consumers are still valid and help you out by pointing in detail where the problems are. As a result, Encapsulation becomes less of an issue, even though it is still preferable to minimize the impact of changes within a module in other modules: You, or other developers maintaining the consumers of your module, will have less to do after changing the module.

It is the case the OutSystems makes quite easy and even performant (due to compiler optimizations) to access entities directly from within screen actions in end-user modules. This should be used carefully though. Suppose that you have a “complex” query in a screen action and you need that same query in two other screens. You could copy-paste it but then you are faced with duplicated code and that is more troublesome to keep in synch once requirements go on changing.

In summary, I am more in favor of option c but the platform makes it easier for you to implement option a.


Getting to the difference you say there is in performance between options a and b, I find it strange. I would expect both to be as performant. You could try to use the ticks component available in the forge to create log lines containing the time it takes to retrieve the data, measured in milliseconds. You can visualize the log lines in service center. Then you have the actual numbers, instead of having to rely on a gut feeling

Solution

Johnny B Good wrote:

I am noticing using the solution b approach that it causes significant lag in fetching data. The user experiences lag in filling the drop down. Drop down is also filled after the user changes a different field which means the user has to wait till drop down is filled. This did not occur in the solution a.

You see this performance hit because for your "possibility a", the platform is optimising what data is fetched (based on what is used on screen) and when using "solution b", there's no such optimisation, so the entire entity data will be return (as opposed to just the data displayed on the dropdown).

Jorge Martins wrote:

In general, for reads, you're probably better off using an Aggregate directly from End-User A that fetches data made Public (but most likely Expose Read-Only) in Core B. This will allow for extra optimisation by the platform when called from the Preparation, for instance.

This is exactly what you are experiencing in your "possibility a".

Solution

thanks all for the info. I got some extra insights into how the platform  is optimising data. It does indeed explain why the solution with the funcitons is slower as it is not prepared and re-fetches the data gain and again when when using an aggregate directly to fill the drop down, even when this is redone after the user changes another field, it will use the 'optimised' data.

Talking about performance, the trade off is usually:

- monolithic will be faster than modular one

- but, harder to maintain.

regards,

indra 

Johnny B Good wrote:

thanks all for the info. I got some extra insights into how the platform  is optimising data. It does indeed explain why the solution with the funcitons is slower as it is not prepared and re-fetches the data gain and again when when using an aggregate directly to fill the drop down, even when this is redone after the user changes another field, it will use the 'optimised' data.

Hi,

The reason fetching data inside an Action is slower is because the platform cannot optimize the generated code based on what data is actually used.

When you use an Aggregate in the Preparation, that then is used on the screen (as the Source Record List for a DropDown, for instance), the platform has enough information to optimize the generated code to only fetch the relevant columns (the ones that are being displayed/used on screen)