6 ms·
While I largely agree with the philosophy, the example provided is not very practical. The code snippet `getExpiredUsers(db.getUsers(), Date.now())` is unlikely
by novoreorx 1y ago
While I largely agree with the philosophy, the example provided is not very practical. The code snippet `getExpiredUsers(db.getUsers(), Date.now())` is unlikely to occur in real-life scenarios. No one would retrieve all users and then filter them within the program. Instead, it should be `db.getExpiredUsers(Date.now())`.
We should never be too extreme on anything, otherwise it would turn good into bad.
- doix 1y ago> No one would retrieve all users and then filter them within the program. No one _should_ do that, but that's a common enough problem (that usually doesn't get found until code is running in production). I suspect with the rise of vibe coding, it's going to happen more and more.
- regularfry 1y agoSometimes it's forced by using the wrong database in the first place, or the wrong data structure. It can be less pain to do a bit of post-processing in the application layer than to unpick either of those.
- _flux 1y agoWith a good library you could do just that, by having the functions return only queries and then expand them to the actual values (by interacting with the DB) after applying the filtering to it?
- soulofmischief 1y agoSo would you then have to do `getActualUsers(db.getUsers())` or `query(db.getUsers())`? Still smells like in such a case the developer avoids the complications of abstraction or OOP by making the user deal with it. That's bad API design due to putting ideology before practicality or ergonomics.
- bulatb 1y agoYou would have an API that makes the query shape, the query instance with specific values, and the execution of the query three different things. My examples here are SQLAlchemy in Python, but LINQ in C# and a bunch of others use the same idea. The query shape would be: active_users = Query(User).filter(active=True) That gives you an expression object which only encodes an intent. Then you have the option to make basic templates you can build from: def active_users_except(exclude): return active_users.filter(User.id.not_in(exclude) ...where `exclude` is any set-valued expression. Then at execution time, the objects representing query expressions are rendered into queries and sent to the database: exclude_criterion = rude_users() # A subquery expression polite_active_users = load_records( active_users_except(exclude_criterion) ) With SQLAlchemy, I'll usually make simple dataclasses for the query shapes because "get_something" or "select_something" names are confusing when they're not really for actions. @dataclass class ActiveUsers(QueryTemplate): active_if: Expression = User.active == true() @classmethod excluding(cls, bad_set): return cls( and_( User.active == true(), User.id.not_in(bad_set) ) ) @property def query(self): return Query(User).filter(self.active_if) load_records( ActiveUsers.excluding(select_alice | select_bob).query )
- t_mahmood 1y agoAs in Django querysets. But starts to get messy with complex queries.
- bulatb 1y agoIt can. SQLAlchemy has good support for types since 2.0, which helps a lot.
- soulofmischief 1y agoThis is a better story because it has consistent semantics and a specific query structure. The db.getUsers() approach is not part of a well-thought-out query structure.
- 1y ago
- fulafel 1y agoDepends on the programming environment: if it's a O(n) operation anyway, meaning you don't the times indexed, and your computation is colocated with the data and the db interface is using lazy sequences... (Also, real-life systems of course do things inefficiently all the time)