logoalt Hacker News

vbezhenar01/21/20254 repliesview on HN

An alternative is to add all dependencies explicitly into function argument list or object fields, instead of using them implicitly from the context, without documentation and static typing. Including logger.


Replies

kgeist01/21/2025

I already talked about it above.

Main problems with passing dependencies in function argument lists:

1) it pollutes the code and makes refactoring harder (a small change in one place must be propagated to all call sites in the dependency tree which recursively accept user ID/tenant ID and similar info)

2) it violates various architectural principles, for example, from the point of view of our business logic, there's no such thing as "tenant ID", it's an implementation detail to more efficiently store data, and if we just rely on function argument lists, then we'd have to litter actual business logic with various infrastructure-specific references to tenant IDs and the like so that the underlying DB layer could figure out what to do.

Sure, it can be solved with constructor-based dependency injection (i.e. request-specific service instances are generated for each request, and we store user ID/tenant ID & friends as object fields of such request-scoped instances), and that's what we had before switching to contexts, but it resulted in excessive allocations and unnecessary memory pressure for our highload services. In complex enterprise code, those dependency trees can be quite large -- and we ended up allocating huge dependency trees for each request. With contexts, we now have a single application-scoped service dependency tree, and request-specific stuff just comes inside contexts.

Both problems can be solved by trying to group and reuse data cleverly, and eventually you'll get back to square one with an implementation which looks similar to ctx.Context but which is not reusable/composable.

>Including logger.

We don't store loggers in ctx, they aren't request-specific, so we just use constructor-based DI.

show 4 replies
cle01/21/2025

> instead of using them implicitly from the context, without documentation and static typing

This is exactly what context is trying to avoid, and makes a tradeoff to that end. There's often intermediate business logic that shouldn't need to know anything about logging or metrics collection or the authn session. So we stuff things into an opaque object, whether it's a map, a dict, a magic DI container, "thread local storage", or whatever. It's a technique as old as programming.

There's nothing preventing you from providing well-typed and documented accessors for the things you put into a context. The context docs themselves recommend it and provide examples.

If you disagree that this is even a tradeoff worth making, then there's not really a discussion to be had about how to make it.

show 1 reply
bvrmn01/21/2025

You can't add arguments to vendor library functions. It's super convenient to have contexted logging work for any logging calls.

danudey01/21/2025

Other responses cover this well, but: the idea of having to change 20 functions to accept and propagate a `user` field just so that my database layer can shard based on userid is gross/awful.

...but doing the same with a context object is also gross/awful.