I read a lot of examples where I read code like this:
def get_user_by_id(session, id)
...
and the function that calls that function needs to create or get the DB session a pass to the function.
the question is why the general design is doing that instead of creating or getting sessions inside the function?
Thanks.
1
Why let people pass in a session
rather than build it for them?
So that the function is also useful in contexts where session
already exists.
If it doesn’t they can always build it and pass it.
Design it the other way around and they’re stuck always making a new one. Even when they don’t need it.
That isn’t to say that it wouldn’t be nice to also have this:
def get_user_by_id(id)
Which would make the session
for you. It’s just really nice to also have the other which lets you reuse an existing session
. As a plus you can design this one so it uses the other and avoid duplicate code.
If you want to do more research on externalizing variables like this it goes by the names: parameterize, reference passing, and dependency injection.
Why so many names? Because a good idea is never allowed to only have one simple name.
2
Lets assume “creating or getting sessions inside the function” is the way at start.
For any real world API, get_user_by_id
is not going to be the only method is it?
There are likely to exist similar methods: get_posts_by_user(post_id)
, get_comments_for_post(user_id)
, like_post(post_id)
and so on.
Would it make sense to duplicate session creation code everywhere ?
Obviously not. Lets suppose we extract the common logic regarding creation or retrieval of a, DB or Authentication, session in session_provider.get_session()
.
How are all the API methods going to get session_provider
in the first place ?
Well, it can be saved in globally accessible state or even can be a singleton. It is general consensus that both global state and singletons are best avoided.
The remaining option is to pass the session_provider
from outside (by whomever that wants to call the API methods.) Also known as providing dependencies externally.
Lets suppose the session_provider
is passed to the constructor of API class having all these methods:
class api {
constructor(session_provider){...}
get_user_by_id(user_id){
session = this.session_provider.get_session();
...
}
}
So far so great. Now let’s focus how other code uses the API. Suppose a request handling function:
handle_get_user_request(http_request){
provider = new session_provider();
api = new api(provider);
user_id = http_request.get_param('id');
return api.get_user_by_id(user_id);
}
Are not we doing the same mistake again ? Creating the dependencies of a function inside it!
Considering we fully embrace the “providing dependencies externally” concept, we would have code where, the things required to compute something, are passed to the function doing the computation:
handle_get_user_request(http_request, api, session){
user_id = http_request.get_param('id');
return api.get_user_by_id(session, user_id);
}
Here we are, with get_user_by_id(session, user_id)
version of the function. Welcome to Dependency Injection.