Is it a good idea to call a service method from another service class ?
class AbcService
def initialize(user_id)
@user = User.find_by_id user_id
end
def find_abc
# some code here
XyzService.new().find_xyz
end
end
class XyzService
def find_xyz
# some code here
end
end
CodePudding user response:
Yes, It is okay to call a service method from another service class. In order to keep your code DRY you can do that.
def find_xyz
# some code here
end
If you need this code, multiple times and you want to keep your code structure clean you can do this.
CodePudding user response:
Yes, in this case the service XyzService
is called a dependency of AbcService
.
One common pattern is to provide dependencies as optional initialization arguments, and wrapped with getters, in order to ease testing and allow more configurability:
class AbcService
def initialize(user_id, xyz_service: nil)
# set up dependencies
@xyz_service = xyz_service
# rest of initialization
@user = User.find_by_id user_id
end
def find_abc
# some code here
xyz_service.find_xyz
end
def xyz_service
@xyz_service ||= XyzService.new
end
end
And a further step is to allow dependency control from a centralized repository
# Dependency repository
module Repo
DEPS = {
xyz_service: ->{ XyzService.new },
# ...
}
# returns a module with one accessor:
#
# Repo[:xyz_service]
#
# is the same as
#
# module (Anonymous)
# def xyz_service
# @xyz_service ||= Repo::DEPS[:xyz_service].call
# end
# end
def self.[](dependency_name)
Module.new do
define_method(dependency_name) do
@_deps_cache ||= {}
@_deps_cache[dependency_name] ||= Repo::DEPS[dependency_name].call
end
end
end
end
class AbcService
include Repo[:xyz_service]
def initialize(user_id)
@user = User.find_by_id user_id
end
def find_abc
# some code here
xyz_service.find_xyz
end
end
With both the above patterns, you can do the following in your tests:
it 'calls the xyz_service' do
xyz = double(:xyz_service)
allow(subject).to receive(:xyz_service).and_return(xyz)
allow(xyz).to receive(:find_xyz).and_return('foo')
expect(subject.find_abc).to eq('foo')
end
in other words, you can replace dependencies with mocks during testing.