Querying Is A Business Concern: Sample
In a recent post, I said that I believe that querying is a business concern. I gave an example there, but I think that this one is a more appropriate for the point that I am trying to make.
The model is very simple, and is shown below. Please do not mention AzMan, I already know about it.
Given a user, I need to find out whatever is has a particular operation with a particular permission. The permissions enum is a bit flag enumeration, with the usual suspects in increasing order (1,2,4,8).
The really simple thing to would simply move across the object graph, something like:
bool hasPermission =
user.Groups.Exists(grp => grp.Roles
.Exists(role => role.Operations
.Exists(op => op.Permissions == requiredPermission)));
I use Linq because it looks cool, here. This is simple, but it is also extremely ineffiecnt. It is much better to let the database do what it is good at, and run a query to get the result back.
Here is my first attempt.
select op from Operation op, Group group
where op.Name = :operationName
and (op.Permissions & :requiredPermission) != 0
and :user in elements(group.Users)
and op.Role in elements(group.Roles)
It resulted in pretty good SQL, but the op.Role in elements(group.Roles) bothered me. That is easy to write, but in SQL that translated to:
and permissions1_.Role in (select role1_.Id from
Roles role1 where role1.Group = grp1_.Id)
Looking into the execution plan, I thought that I could do better, and I re-wrote the query to be a bit
select op from Group group
join group.Users user
join group.Roles role
join role.Operations op
where user = :user
and (op.Permissions & :requiredPermission) != 0
and op.Name = :operationName
This turned out to be much more efficent (two clustered index scans less).
Anyway, I am letting the technology sway me away from a architectual discussion. This query is sitting on an AuthorizationService, and I consider it a 100% business logic.
Comments
Ayende,
Although it might be inefficient, is there a tradeoff here depending on the situation?
I wonder as well, does this Linq query get cached ?
I do however agree with your comments, this is very much business logic!
shouldn't the "process" of obtaining the permissions be the business logic, but the actual query mechanics be service logic? I believe there is a distinct difference between the two, although the implementation may join them via interface vs. implementation.
I very much agree with the architecture proposed by Derick.
@SteveG,
Just the first query is using Linq, the other two are HQL queries.
There are no real trade offs between the two queries, except for efficiencies.
Yes, you can cache this query
@Derick,
Can you define what is a query mechanics in this case?
The code that I am using is:
ICollection<Permission> permissions = Repository<Permission>.FindAll("query", parameters);
return permissions.Count != 0
Ayende,
The code that you post in your comment is what I would consider to be the mechanics - the service portion of the call - and should be hidden behind an interface. My basic reasoning for making this statement is encapsulation - to allow the mechanics of the query to change, without the semantics of the call changing.
This becomes even more important in unit testing. If I am going to test the CRUD permissions of a Controller for a screen, I need to be able to mock the CRUD call to return an expected result, without having the real permission store set up and connected.
public interface IPermissionService
{
public ICollection<Permission> GetPermissions();
}
public class PermissionService: IPermissionService
{
public ICollection<Permission> GetPermissions()
{
}
}
Did notice that the query is in the authorization service?
The only logic in the IsAllowed() method is in the query, so to test that I need a DB anyway.
If I don't want that, I can mock the authorization service directly.
Ayende Makes The Statement that Querying Is A Business Concern. I have often made this same statement...
This turned out to be a semantic misunderstanding on my part, not a design or implementation difference. My appologies for the misunderstanding.
No problem.
I am glad to know that we agree with each other.
Comment preview