Avoiding data layer calling business layer

Avoiding data layer calling business layer

Old forum URL: forums.lhotka.net/forums/t/4203.aspx


vdhant posted on Tuesday, January 22, 2008

Hi guys

In the situation where i want to define the authorisation (which roles can access what) and business rules information for a particular class from the database, in the situation where data portal is going over the server (or the data portal is cloning the object) how do i do this without the data layer calling methods in the business layer.

In that to get the rules/definitions from the database I would have a list and item class. From the AddAuthorizationRules (or any of the other rules or authorization methods) I would be requesting that the list return a list of information that i would loop through and add the roles or rules. To do this i would be calling the fetch on the list class (which is in the business layer) and in turn it would call the data_fetch method (which is in the data layer). The problem is that because the security information is NonSerialized, the AddAuthorizationRules methods will run in both the business layer and the data layer. Now this is expected but if from within these methods I am calling the fetch method of another object I am going to get the case where the data layer is calling the business layer which was/is always a big no no in my books.

Does anyone have anyways that they deal with these situations or any suggestions??
Thanks
Anthony

jureleskovec replied on Tuesday, January 22, 2008

First of all, you should use/override 'AddInstanceBusinessRules' and 'AddInstanceAuthorizationRules' methods but  instead the  'AddBusinessRules' and 'AddAuthorizationRules' when dealing with the dynamic rules/roles.

I wouldn't suggest you to call any factory (server-side) methods from those methods since they are called after the object creation, deserialization, in the UndoChange method and maybe elswhere too. This means that you can never be sure where your code in those two methods is currently running.

I would suggest you to consider your roles/rules not as much as methods but as data that is part of the business object itself:

private RoleInfoList _roles;

You could load the 'RoleInfoList' object in the MyBusinessObject.DataPortal_Fetch() method. From the Fetch method you could call RoleInfoList.Get(dataReader1) since this is a child object and it doesn't have its own DataPortal_Fetch method.

In the 'AddInstanceAuthorizationRules' method loop through the '_roles' collection of rules and add them to the ValidationRules collection.

 

-- Jure

vdhant replied on Tuesday, January 22, 2008

It sounds like from what you are saying that I should store my results from the database in a list (I guess I would have two, one for auth. and the other for rules) within the object and then when the corresponding instance methods run I then loop through and do the population from this list.

If this is the case am I not adding the overhead of sending that extra information around the network? Also the reason why this is an issue is because in the framework the fields that currently store that same data we are talking about storing is marked as NonSerialized. I am guessing that this is done for a reason and wouldn't that same reason be valid here?

Thanks
Anthony

tmg4340 replied on Tuesday, January 22, 2008

There have been a couple of other threads regarding working with business rules/authorization rules coming from a database.  Rocky has commented on a couple of them, and the one overriding rule he gives is:

Don't make any database calls in any CSLA method other than the DP_ methods.

(Obviously, I added the emphasis, but I think Rocky's post also used a larger font - he really wanted to make sure the point was made.  Smile [:)])

The solution that's usually given is to create another BO to collect your authorization/business rules from the database.  It could be a Command object, or it could be a ReadOnlyList.  In any event, that allows you to make the database calls in that object's DP_ methods, return the results, and then process them in the appropriate methods in your other BO.

Yes, it's another object, and another DP_ trip, and another database call... but if you need the flexibility of storing that information in the database, you're going to have to take the performance hit somewhere.  It's probably not going to be as bad as you think, either - you should already have a database connection to use, and the list of rules should be relatively small.  As you've noted, if you collect this information with the rest of your BO data, you're going to have to store it somewhere so that you can use it in other methods.  And you can't mark those variables as NonSerialized, because authorization/business rules are re-hooked on the client side - after the object comes back from the DP.  So if you don't serialize your data, you can't re-hook the rules.  That's why the built-in fields are marked as NonSerialized - it's a trading-space-for-time optimization in the framework.

vdhant replied on Tuesday, January 22, 2008

As i see it I have three options

Option A
Workings

If when the class gets called through any of the static methods (if i use that pattern) or when I create the object, when it is still on the client (where ever that may be) I call another client objects fetch method to return the list. I then run through the list while I am still on the client, populate the rules/auth on the client and then store the results. The server is then called which uses the stored data to results to populate the internal rules/auth list.

Bad Side
Because the results are being stored, it means the the server is trusting the results coming from the client. If I am going over the network using XML or something this means that the packet could be changed to change the auth/rules. This is a big bad in my books.

Good Side
Client class are only calling client classes. Good performance as the DB is only hit once.

Option B
Workings

When the class goes to populate the auth/rules list (regardless of whether we are on the client or server) we go to the another client objects fetch method to return the list.

Bad Side
We have server methods (i.e. data_xyz) calling client methods (i.e. xyz) which is what I am trying to avoid. Also with the way the current Data Portal works even though I am on the server I would try and go across the network again when it is already on the server. To not have this problem (and just quickly looking i can't see that it does) the portal would have to check to see if the calling is coming from a method that is already on the server.

Good Side
We don’t have any trust issues.

Option C
Workings

Logically when retrieving the list of rules/auth, logically i call a command class (which may or may not be a CLSA concept of a command class) which determine where it is executing and acts accordingly. This determination might not happen in the class its self but rather the data portal.

Bad Side
Not sure if the framework caters for this logical/physical scenario.

Good Side
We don’t have any trust issues. Logically I don’t have to worry about data methods calling business methods. Seems to be the best of all worlds.

Well that’s my brain picked. Does anyone have any ideas.
Thanks
Anthony

jureleskovec replied on Wednesday, January 23, 2008

vdhant:
Also the reason why this is an issue is because in the framework the fields that currently store that same data we are talking about storing is marked as NonSerialized.

Like tmg4340 said. You generally don't need to serialize rules/roles because these are hardcoded and framework can reastablish them on either side by invoking those two methods. But since your rules/roles are stored in database you will have to bring this data to the client in any case.

So, the question is only how/when and in what form.

You could also use a Command object to load these data before the DataPortal_Fetch has been called as additional data in the factory method of the BO. But I don't see the point of doing it, since this would mean another round trip to server which could be accomplished in only one. And I see this solution would also be more complicated.

I see the rules/roles data as child BOs which are part of the root BO, much like the objects Project (root) and ProjectResources (child) in the ProjectTracker.Library. So I can still see this the only solution.

I can assume these dynamic rules/roles should be part of all your BOs. In this case you could create base business object class that would already include all the mechanisms for acquiring and adding roles/rules, and derive all BOs from it/them.

 

-- Jure

tmg4340 replied on Wednesday, January 23, 2008

jureleskovec:
Like tmg4340 said. You generally don't need to serialize rules/roles because these are hardcoded and framework can reastablish them on either side by invoking those two methods. But since your rules/roles are stored in database you will have to bring this data to the client in any case.

So, the question is only how/when and in what form.

You could also use a Command object to load these data before the DataPortal_Fetch has been called as additional data in the factory method of the BO. But I don't see the point of doing it, since this would mean another round trip to server which could be accomplished in only one. And I see this solution would also be more complicated.

I see the rules/roles data as child BOs which are part of the root BO, much like the objects Project (root) and ProjectResources (child) in the ProjectTracker.Library. So I can still see this the only solution.

I can assume these dynamic rules/roles should be part of all your BOs. In this case you could create base business object class that would already include all the mechanisms for acquiring and adding roles/rules, and derive all BOs from it/them.

The only potential issue with that approach is if your rules are per-type and not per-instance.  Under CSLA, the default for business & authorization rules is per-type.  Adding that data as part of the BO's DP_Fetch/DP_Create means it is getting loaded with every object, instead of once per type.  So the savings of loading the rules in the same DB call as the object's other data is lost because you're loading the same data over and over, which most of the time won't be used.  The per-type rules are statically stored, so they are only loaded once, and don't necessarily need to get re-hooked (and thus re-loaded) on every deserialization.

Now, if you have per-instance rules, then there could certainly be some benefit using this scheme.

Lastly, things like authorization rules often don't have much to do with the data in the object, and more to do with the user's rights in the application.  So keeping the data separate offers a more loosely-coupled design.  Several different BO's could use the same authorization set, so including it with each BO load could be a duplication of effort.

- Scott

vdhant replied on Wednesday, January 23, 2008

Thanks for the reply.

Just one thing though, what about the fact that if you are storing the auth data as child data within the object, it means that the server is trusting that this data coming from the client is correct and has not been tampered with. If the information is coming over the network as XML its not that had to edit the data to say that a user now has access who shouldn't and the server would allow this because with this pattern the server is trusting that the object has the correct data. So if clients in this case are untrusted, I would have thought that this was not the best idea. But that said I dont think CSLA checks the rules and auth once it has gone through the data portal and is on the server - which i would have thought it should be doing considering trust issues.

Thanks
Anthony

tmg4340 replied on Wednesday, January 23, 2008

Rocky, and others, have discussed the issue of trust in several threads lately.  It seems a lot of folks are worried about the trust of the data that's being passed around...

CSLA's design is predicated on the fact that the two machines that are talking - the client and the server - inherently trust each other.  If you feel that you can't trust the app server that you're talking to, then you have to approach your design that way.  That means that you code a trust boundary into your design, be it via a web-service separation or other (likely SOA-style) means.  That also means you could spend a fair amount of time developing procedures to more securely transmit your data, translate it between your DTO's and your BO's, and verify the data on both ends.  That will also affect the performance of your application, and it will also affect the design of your BO's.  CSLA is not optimized to work under those conditions.

From my perspective, if you're going to do that, then you can't store your business rules & authorization rules in the database.  If you can't trust your app server, then you shouldn't make the rules that govern how your application functions, and what sort of access any user of the application has, accessible only from that untrusted source.  That effectively cripples your client application, as it literally has no recourse in the case of data corruption.

This is, of course, only my opinion.  As I said before, this has been discussed in several threads lately - just do a search on the forums and you'll find them.

- Scott

jureleskovec replied on Thursday, January 24, 2008

tmg4340:
Lastly, things like authorization rules often don't have much to do with the data in the object, and more to do with the user's rights in the application.

Yes, I agree. The notion of rules/roles as children of BOs that I tried to sell is wrong. But still you could stick to my solution.

tmg4340:
Adding that data as part of the BO's DP_Fetch/DP_Create means it is getting loaded with every object, instead of once per type.

But they have to be loaded with every root object anyway, one way or another. And since these are dynamic rules/roles you can't use per-type rules/roles.

Scott, could you describe your solution in more detail. As an alternative I only see the one that makes another round-trip via the Command object, and this doesn't seem reasonable to me.

 

-- Jure

tmg4340 replied on Thursday, January 24, 2008

jureleskovec:
But they have to be loaded with every root object anyway, one way or another. And since these are dynamic rules/roles you can't use per-type rules/roles.

Scott, could you describe your solution in more detail. As an alternative I only see the one that makes another round-trip via the Command object, and this doesn't seem reasonable to me.

Just because I store the business rules in a database doesn't mean they are completely dynamic.  And as I said, if they are dynamic (i.e. instance-related), then you certainly could package the rules with the BO when you collect it.  Under those circumstances, I might say you should.

But I might need to create per-type rules that users of my application can manage.  Every BO would work the same way - thus, per-type rules - but the users (or, more appropriately, administrators) could manage those rules.  Or, perhaps I want a way to abstract the rules such that changes to the business are still done by me (or my team), but don't require a new application version.

This implementation doesn't require that the rules be loaded on every instance - indeed, you probably wouldn't want to.  Consider this - you have one Order object open, and while you have that open, an administrator changes the rules for Order objects.  If you open another Order object, and attach the new rules to it, you now have one of two potentially-confusing situations:

1. Two Orders operating under different business rules, if the rules are stored per-instance.  I've honestly thought that per-instance business/authorization rules would be a recipe for disaster anyway.  I know there can be valid reasons for it, but unless that information is clearly displayed to the user, I believe they'd more often than not be left scratching their heads when using your app.  Plus, it's entirely possible that the rules were changed such that the first Order would have been invalid - and thus not savable - had that Order been operating under the new rules the entire time.  But that Order has no knowledge of the new rules, and thus happily allows the user to save an invalid set of data.  This last issue could also apply to someone who only opens one Order, but does so before the new rules are saved.

2. Your first Order spontaneously changing its status, if the rules are stored per-type.  It could be very disorienting to the user to switch back to the first Order and suddenly see a bunch of errors on a screen they did nothing to.  Plus, now you add the overhead of an object registry, so you can develop a notification scheme to tell all the open Order objects to re-validate themselves on demand.

The mechanics of dealing with this situation - changes to the business rules mid-stream - can be frustratingly complex, no matter how you manage your database rules.  The easiest solution usually involves a human element - i.e. coordination among users and administrators to clear people out of the application, modifying the rules only at certain times of the day, or an understanding that people need to re-start the app.  Other options often include some sort of daily job that re-processes all data in the database according to the current business rules, flagging those that have become invalid.  That can significantly alter your object, and application, design, depending on the requirements to fix those errors.  Or, you can design your "middle tier" to re-validate every object before it's saved, thus providing an extra level of protection.  That won't entirely eliminate the problem, but it will help.  The downside is that it increases your network traffic, as you have to send the object to the server, which then sends it right back with new rules - which are then stored on the client, which then performs yet another re-validation to get the current list of errors.  It's not too long before you consider not sending the rules to the client at all - after all, you can't trust what's there.

As for the alternative, you are correct - it does involve another round-trip, either with a Command object or a ReadOnlyList.  But for per-type rules, you would only do that once per type, so the overhead is fairly minimal.  If you wanted, you could develop a process that loads them all on startup - users are more likely to accept a longer startup time over a performance drain at (what appears to them to be) unpredictable points in the app.  Then you could get them all in one trip.

- Scott

jureleskovec replied on Friday, January 25, 2008

tmg4340:
But I might need to create per-type rules that users of my application can manage.  Every BO would work the same way - thus, per-type rules - but the users (or, more appropriately, administrators) could manage those rules.  Or, perhaps I want a way to abstract the rules such that changes to the business are still done by me (or my team), but don't require a new application version.

Yes, I can see now what you meant. I didn't even think of such scenario.

Thanks,

-- Jure

vdhant replied on Friday, January 25, 2008

I have to agree with Scott with this one. Per-Type is the way i was going to go and for all the reasons that Scott has described. If the rules are undated during the day, I would have the system run script which updates them in the system overnight. Likewise with the Auth except that there would be the option to force the update in case there was a security concern but i don’t think that this would be a common accordance.
Thanks for all your feedback.
Anthony

Copyright (c) Marimer LLC