Controllers, repositories, services – where do I put the logic?
I could use some advice regarding a project that I am currently working on. The project is a web site, where members can join various groups (did I hear “Facebook already has that”?) and do stuff depending on what groups they belong to.
Let’s ignore all other entities and focus on the Member and Group entities.
I have a repository interface/protocol for each entity. The main repository’s sole responsibility is to communicate with Entity Framework (I also have other implementations of each repository interface, e.g. fake repositories that just create fake data, cache decorators etc.).
I then have service classes (I know, I know…bad name) for more sophisticated functionality. The controller classes of my web site then use the various service classes to do stuff. Service classes can talk to each other, repositories can not.
This architecture can be illustrated (quite simplified) as such:
Now, here is my dilemma…where do I put additional logic?
Say, for instance, that I want to be able to find all members that are in the same groups as a certain member. I could then:
- Add a GetGroupFriends method to the Member entity. The method could then iterate over all the groups that a member is a member of (which would require stepping through Member -> GroupMember -> Group), and return a distinct list of members that are members of the same group.
- Add a GetGroupFriendsForMember method to the MemberService and do mostly the same thing as above, but working with a repository to retrieve data instead of drilling down the member object.
- Add a GetGroupFriendsForMember method to the GroupService and do mostly the same thing as above, but working with the repository to retrieve data instead of drilling down the member object.
- Add a GetGroupFriends method to the Member entity and a GetGroupFriendsForMember method to one of the service classes, and make the service call the entity method.
- Placing the method in the Member entity is simple, and using the method is also quite straightforward, but…
- …it will also cause coupling. Also, the Member entity is not abstract, which means that we cannot mock it that easily (although some mocking frameworks support this)
- Placing the method in a service is also simple and nice approach, especially since each service is abstract, but…
- …which service should then own the logic? I would go for the group service, but I do not really know.