clojure-losangeles

2025-11-14T19:31:26.284179Z

I've been going through the podcast again, and as I was going through Sportify!, I found myself wondering: what, concretely, does it look like to separate out exception handling from io? Specifically, consider a situation where I want to execute a SQL query with next.jdbc/execute!, perhaps to store something in a db. I need to wrap that in a try/catch to handle failure, perhaps because a uniqueness constraint was violated. So, I could write a function that does all that, but now there's no way to test the catch logic (maybe it prepares a particular data structure that can be used to provide an appropriate HTML response) without involving the database. How do I take these apart elegantly? The only thing I can think of is abstracting the execute behind a function and passing either the next.jdbc/execute! call or some "mock" for testing that throws etc. This feels like exactly what I'd do in Java: mocking a repository or something. It also feels weird because that means jdbc stuff is represented in two places: the innermost level where the io actually happens, and the level above where the exception is handled. Maybe a way keeping the jdbc stuff contained is to keep the try/catch in the inner level and just indiscriminately transform exceptions to ex-data or something, which then gets handled higher up as a more "domain oriented" exception message? That doesn't let me test the exception logic where the execute happens, but since it's a very straightforward adapter, it doesn't matter; I can test the "real" handling by just faking the db part to return/throw the right ex-data. Thoughts? Examples of how to do this nicely?

seancorfield 2025-11-17T18:10:32.292719Z

I think there are lots of different ways to approach this but it really depends on what "recovery" logic you have and what "level" you want (in terms of call stack depth). At work, we mostly let SQL exceptions just bubble up, except where we "expect" an exception that we have very localized recovery handling for, such as a constraint violation where we might be doing insert-or-update but the straight insert is the normal path and the update is specialized for rare cases where we could have a duplicate. We test that by deliberately causing a duplicate insertion, and we use a throwaway test database for our test suite already. (fn [f args handle-ex] (try (f args) (catch SomeException e (handle-ex e)))) might be a reasonable way to do some of that in a more generic fashion if that is appropriate for your use case and it's easily testable. I'm just not convinced there's a truly generic approach that works here.

seancorfield 2025-11-17T18:11:18.927659Z

As with many things, I think this is one of those "it depends" cases tho'...

nate 2025-11-17T23:36:23.334749Z

So true!

seancorfield 2025-11-14T20:13:40.690319Z

I have lots of thoughts/suggestions but I'm at Conj so I'll follow up when I get back.

2025-11-16T14:25:53.221919Z

Looking forward to it. Thanks!