I'm curious to know your opinion on how to organize code in services and repositories in the context of a three-layer architecture (controllers - services - repositories).
Let's say there is a User
data model with fields id
, first_name
, last_name
and some other models related to User, for example,
Address
, Child
, Parent
and so on.
Let's say there is a repository (pseudocode):
class UsersRepository:
def create_user(...):
# can be used in multiple endpoints
pass
def update_user(...):
# can be used in multiple endpoints
pass
def delete_user(...):
# can be used in multiple endpoints
pass
def add_child(...):
pass
def find_user_by_id(...):
# can be used in multiple endpoints
pass
def find_users_with_one_child_only(...):
# this method is being used in a single endpoint (controller, view)
pass
def find_users_without_address(...):
# this method is being used in a single endpoint (controller, view)
pass
# another methods
And there is a service (pseudocode, so far one for multiple endpoints (views, controllers):
class UsersService:
users_repository = UsersRepository()
def create_user(...):
user = users_repository.create_user(...)
return user
def add_child(...):
user = users_repository.find_user_by_id(...)
child = users_repository.add_child(user, child)
return child
def find_users_with_one_child_only(...):
# this method is being used in a single controller (view)
users = users_repository.find_users_with_one_child_only(...)
return users
def find_users_without_address(...):
# this method is being used in a single controller (view)
users = users_repository.find_users_without_address(...)
return users
And there are 2 controllers (view in the python world):
repository = UsersRepository()
service = UsersService(repository)
POST /users
def create_user_controller(...):
return service.create_user(...)
GET /users-with-one-child-only
def users_with_one_child_only_controller(...)
return service.find_users_with_one_child_only(...)
GET /find-users-without-address
def find_users_without_address_controller(...)
return service.find_users_without_address(...)
You can see that the methods UsersService.find_users_with_one_child_only
, Service.find_users_without_address
, as well as the methods UsersRepository.find_users_with_one_child_only
, UsersRepository.find_users_without_address
that they call, are used only once. And in the create_user_controller
controller.
Of course, the number of such one-time used methods will most likely increase over time.
Can this be used? It seems that such a class will turn into a god object (both UsersService and UsersRepository)? If this is not desirable, then how can it be prevented?
Do we need to create a separate service and repository for each endpoint? For example:
class UsersWithOneChildOnlyRepository:
def find_users_with_one_child_only(...):
# this method is being used in a single controller (view)
pass
# of course there can be many another methods
class UsersWithOneChildOnlyService:
def find_users_with_one_child_only(...):
# this method is being used in a single controller (view)
users = users_repository.find_users_with_one_child_only(...)
return users
repository = UsersWithOneChildOnlyRepository()
service = UsersWithOneChildOnlyService(repository)
GET /users-with-one-child-only
def users_with_one_child_only_controller(...)
return service.find_users_with_one_child_only(...)
The UsersWithOneChildOnlyRepository.find_users_with_one_child_only
method is excluded from the UsersRepository
repository,
and the UsersWithOneChildOnlyService.find_users_with_one_child_only
method is excluded from the UsersService
service.
The same thing happens with the UsersService.find_users_without_address
and UsersRepository.find_users_with_one_child_only
methods
As a result, only crud methods common to many endpoints remain in the UsersRepository
repository,
while specific one-time methods are moved to their own classes.
What do you think about this?