Suppose you're writing a REST API service. You want to query a data from DB, and if no record found,...
j
Suppose you're writing a REST API service. You want to query a data from DB, and if no record found, you response a 404 not found. I have two possible implementations. 1.
Copy code
class SomethingNotFoundException : Exception()

// DB query side
if (result.isEmpty()) {
    throw SomethingNotFoundException()
}

// API implement
try {
    ....
} catch (e: SomethingNotFoundException) {
    return HttpResponse(404, "SOMETHING_NOT_FOUND")
}
2.
Copy code
// DB query side
if (result.isEmpty()) {
    throw HttpException(404, "SOME_THING_NOT_FOUND")
}
where
HttpException
is handled by the framework or a single
catch
that extracts the response parameter from the exception fields. The second approach is simpler, but it couples with the protocol as you involves the HTTP exception and related concept like status code. Which one would you choose?
s
Using exceptions for control flow sacrifices a lot of compile-time safety. I would actually reject both options and instead have the DB layer return a sealed class with different subclasses for the possible success or failure modes.
2
r
Or in this case just return null, and change that into a 404 at the http layer.
👌 3
j
This is just a simplified version, there might be other exceptions so null is not enough, but I get your point.
t
personally, I tried returning sealed classes/result/arrow either but compared to exception they pollute the entire trace call. maybe that highlights other design issues, as in too many layers of indirection, but it is a lot less transparent than exception. also, exceptions map into frameworks, for instance with spring we can just have a
@ControllerAdvice
and catch all exceptions that extend a generic
NotFoundException
and transform those into 404
☝️ 1
s
If you can live with exceptions then go for it! My experience is that "transparent" is just another way to say "unsafe and undocumented". The "polluting" that you describe is just what happens when you make a method actually be honest to its consumers about what it can return (because exceptions are just alternate return values).
t
yep, I get it. that is why I tried it, because on paper I like it more (both aesthetically and from a “exception should not be used to model expected failures” standpoint). my reality check (but I guess mine is just one “use case”) are frameworks, which understand exceptions way better, and the fact that in a multi-layer application most of the layers don’t care about most of the expected failures, so each layer just become more complex to just pass the failure through and map the success
j
@thanksforallthefish My question is, if you inherit from, e.g.
NotFoundException
, you are coupling with the framework or protocol. I know it may be a widely used practice but I'm just asking.
t
it might depend on the framework, spring is totally agnostic. you can define your own exception and use that in a controller advice. then your domain remains isolated from the framework, and you have a mapping layer (the controller advice) which map from domain exception to framework, but that layer is where it needs to be
r
If you're going to use exceptions there's a 3rd option - throw your own
NotFoundException
with no HTTP or framework specific coupling, and use your framework's capabilities to map that exception to an HTTP response. Then the coupling is between the framework's HTTP layer & your domain, rather than coupling the data access to HTTP and/or the framework.
Beat me to it by a second or two...
📸 1
c
I would use null for not found, because thats not an exceptional case, and an exception when something goes wrong
t
I would also go for an either, but if you do exceptions I would create my own domain exceptions and transform those to http exceptions in the controller. This way the place that throws the exceptions doesn't have to know about it being run in a http environment (so it can be more easily be reused/tested )
c
imo sealed classes + nullable is more idiomatic kotlin than an either type. and exception for anything that can just bubble up (and result in an http error)
t
How would the sealed classes + nullable look like?
c
just using a sealed class hierarchy instead of an either, and also giving meaning to null (for example in a store interface null means not found)
but most of the time i would proably use nullable + exceptions. because null could be something i want to recover from, and all other errors will just fail the whole request. like
user = store.get(key)?: fallbackStore.get(key)