2 added 173 characters in body
source | link

Ideally, I'd find a way to seamlessly annotate values with information about where they came from, without disrupting the models of the inner layers of the architecture.

Ideally, I'd find a way to seamlessly annotate values with information about where they came from, without disrupting the models of the inner layers of the architecture.

1
source | link

How to create useful error messages in a JSON REST API without leaking implementation details across layers?

We're implementing a REST service with three main layers: Controller, Domain, Repository.

The REST API is supposed to respond to error conditions with meaningful error messages. Suppose the general layout of a payload given to a POST request were something like this:

{  "name": { "first": "string", "last": "string" } }

Now, say we have an error somewhere down the line, and name.first is rejected for whatever reason. We're following the JSON API standard and would like to give source pointers to the payload object:

{
  "errors": [
    {
      "status": "400",
      "source": { "pointer": "/name/first" },
      "title":  "Invalid Value",
      "detail": "'Steve' is not a valid name."
    }
  ]
}

But the entities in the repository and domain may look very different from the controller entities.

So, how do I report adequately which fields in the original JSON are affected when an error is raised in the repository?

I'm using Kotlin. Say my repository data structure looks like this:

data class Person(val firstName: String, lastName: String)

whereas in the controller it probably looked something like this:

data class PersonInput(name: Name)
data class Name(first: String, last: String)

So if, say, my data base backend rejects firstName I now need to raise an error about Person.firstName in the repository that the controller needs to map to its personInput.name.first value, and report the correct { "source": { "pointer": { "/name/first/" } }. It can't just report firstName. The user has never seen it, and that's an implementation detail of my repository.

Right now, all options I have are kind of bad:

Raise very specific errors, that are always mapped to a particular REST input field

That's an OK approach for few and simple data structures, but it breaks down once the data structures become dynamic, or even overly complex, since you'll end up with a zoo of enums/error types.

Annotate all values that pass down to the controller with source information

Since I can't have annotations with retention RUNTIME and target EXPRESSION, i.e. I can't "enhance" values by stealthily adding a field via reflection, I'd need to wrap every value in my entire logic everywhere with a special type, say

data class Source<T>(val content: T, val source: String)

This adds a ton of indirection everywhere and makes my domain and repository hellish to test, as I need always wrap everything in pointless source pointers.

Have a look up table that co-ordinates domain and repository entities with REST source pointers

The idea would be to map repository entity fields and domain entity fields to controller entity fields in an explicit lookup table, whether through strings or even reflection (property names.)

Now there's an object in my program that knows about all layers, and breaks every time I change anything in any of the layers. That's not good!


Bottom line: We're stumped. How do we create useful error messages for our clients without breaking architecture encapsulation between layers? We haven't found good answers on the Net.