I'm refactoring an application into service objects to keep the models smaller and the actual code files more organized.
To that end, I'm utilizing service objects that perform actions on a given object. However, one of the dilemmas I'm facing is whether I pass in the entire object to the service or just an ID.
A classic example is updating a user's email address. The reason I would use a service object instead of just a method in the model, is that this email also has to sync with external 3rd-party systems that do not belong in the user model.
class User::UpdateEmail
# Passing entire user object
def self.update_for_user(user, new_email)
if user.update_attributes(email: new_email)
# do some API calls to update email in external services
true
else
false
end
end
# Just passing user ID
def self.update_for_user_id(user_id, new_email)
user = User.find(user_id)
if user.update_attributes(email: new_email)
# do some API calls to update email in external services
true
else
false
end
end
end
Is there any benefit to one vs the other? They both "seem" to do the same thing and it just looks like personal preference to me, but I'm curious if I would run into a situation where I pass in an entire user object and somehow it gets stale while the service object works on it.
If I pass in the entire user, then the class that calls the service object would need to do the checking to ensure the user exists and so forth, whereas if I just pass a user_id then the service object now has to ensure a valid object, etc.
is there an agreed upon or expected standard or pattern for this type of service-oriented operation? I want to make sure I use a consistent approach throughout the entire application and also don't force myself into a hole later on.
Well... this depends:
If you are using background jobs you should only pass ids! If you are not doing that... maybe objects, and always ask your self who/what is responsible for it.
If the object you are passing the thing to is the one responsible and must know about it then maybe that object should take care of the lookup of it and just an id would be enough. The lookup will happen in that responsible object.
If the object you are passing the thing to takes any object and does not care what kind it is because it handles it dynamically. (as it comes) then maybe the entire object. depending on how big it is...
You have to be able to judge this case by case, there is no
1-general-rule-or-solution
except for that each object should have a single responsibility. And even that can be bent or broken in rubyIn general:
Try to keep things loosely coupled, clear and simple, with each thing its single responsibility. Avoid tying things together that don't need to know about each other or be tied up together.
I recommend you read:
Practical Object-Oriented Design
in Ruby by Sandy MetzIt has good examples and explains these concepts in a clearly.